mirror of https://github.com/OpenVidu/openvidu.git
openvidu-pro: Adapt openvidu-browser and openvidu-server ce to send browser logs related with openvidu-browser in OpenVidu Pro
parent
da3fb64073
commit
5841d15a86
|
@ -1322,6 +1322,10 @@ export class Session extends EventDispatcher {
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Configure JSNLogs
|
||||||
|
OpenViduLogger.configureJSNLog(this.openvidu, this.sessionId, response.id, token);
|
||||||
|
|
||||||
|
// Process join room response
|
||||||
this.processJoinRoomResponse(response);
|
this.processJoinRoomResponse(response);
|
||||||
|
|
||||||
// Initialize local Connection object with values returned by openvidu-server
|
// Initialize local Connection object with values returned by openvidu-server
|
||||||
|
@ -1461,7 +1465,6 @@ export class Session extends EventDispatcher {
|
||||||
|
|
||||||
this.openvidu.wsUri = 'wss://' + url.host + '/openvidu';
|
this.openvidu.wsUri = 'wss://' + url.host + '/openvidu';
|
||||||
this.openvidu.httpUri = 'https://' + url.host;
|
this.openvidu.httpUri = 'https://' + url.host;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.error('Token "' + token + '" is not valid')
|
logger.error('Token "' + token + '" is not valid')
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Logger = console;
|
var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
|
||||||
|
var Logger = OpenViduLogger.getInstance();
|
||||||
|
|
||||||
var MAX_RETRIES = 2000; // Forever...
|
var MAX_RETRIES = 2000; // Forever...
|
||||||
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
|
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
|
||||||
|
|
|
@ -1,12 +1,87 @@
|
||||||
|
import {JL} from 'jsnlog'
|
||||||
|
import {OpenVidu} from "../../OpenVidu/OpenVidu";
|
||||||
|
|
||||||
export class OpenViduLogger {
|
export class OpenViduLogger {
|
||||||
|
|
||||||
private static instance: OpenViduLogger;
|
private static instance: OpenViduLogger;
|
||||||
|
|
||||||
|
private JSNLOG_URL = "/openvidu/elk/openvidu-browser-logs";
|
||||||
|
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 50;
|
||||||
|
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
|
||||||
|
|
||||||
|
private openvidu: OpenVidu;
|
||||||
private logger: Console = window.console;
|
private logger: Console = window.console;
|
||||||
private LOG_FNS = [this.logger.log, this.logger.debug, this.logger.info, this.logger.warn, this.logger.error];
|
private LOG_FNS = [this.logger.log, this.logger.debug, this.logger.info, this.logger.warn, this.logger.error];
|
||||||
private isProdMode = false;
|
private isProdMode = false;
|
||||||
|
private isJSNLogEnabled = true;
|
||||||
|
private isJSNLogSetup = false;
|
||||||
|
|
||||||
|
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure http uri to send logs using JSNlog
|
||||||
|
*/
|
||||||
|
static configureJSNLog(openVidu: OpenVidu, sessionId: string, connectionId: string, token: string) {
|
||||||
|
// If instance is not null, JSNLog is enabled and is OpenVidu Pro
|
||||||
|
if (this.instance && this.instance.isJSNLogEnabled && openVidu.webrtcStatsInterval > -1) {
|
||||||
|
this.instance.info("Configuring JSNLogs.");
|
||||||
|
try {
|
||||||
|
this.instance.openvidu = openVidu;
|
||||||
|
|
||||||
|
// Use connection id as user and token as password
|
||||||
|
const openViduJSNLogHeaders = (xhr) => {
|
||||||
|
xhr.setRequestHeader('Authorization', "Basic " + btoa(connectionId + ":" + token));
|
||||||
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
||||||
|
// Additional headers for OpenVidu
|
||||||
|
xhr.setRequestHeader('OV-Connection-Id', connectionId);
|
||||||
|
xhr.setRequestHeader('OV-Session-Id', sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customAppender: any = JL.createAjaxAppender("openvidu-browser-logs-appender-" + connectionId);
|
||||||
|
customAppender.setOptions({
|
||||||
|
beforeSend: openViduJSNLogHeaders,
|
||||||
|
batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
|
||||||
|
maxBatchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
|
||||||
|
batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES
|
||||||
|
});
|
||||||
|
|
||||||
|
// Avoid circular dependencies
|
||||||
|
const logSerializer = (obj): string => {
|
||||||
|
const getCircularReplacer = () => {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
return (key, value) => {
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
if (seen.has(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.add(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return JSON.stringify(obj, getCircularReplacer());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize JL to send logs
|
||||||
|
JL.setOptions({
|
||||||
|
defaultAjaxUrl: OpenViduLogger.instance.openvidu.httpUri + this.instance.JSNLOG_URL,
|
||||||
|
serialize: logSerializer
|
||||||
|
});
|
||||||
|
JL().setOptions({
|
||||||
|
appenders: [customAppender]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.instance.isJSNLogSetup = true;
|
||||||
|
this.instance.info("JSNLog configured.");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error configuring JSNLog: ");
|
||||||
|
console.error(e);
|
||||||
|
this.instance.isJSNLogSetup = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static getInstance(): OpenViduLogger {
|
static getInstance(): OpenViduLogger {
|
||||||
if(!OpenViduLogger.instance){
|
if(!OpenViduLogger.instance){
|
||||||
OpenViduLogger.instance = new OpenViduLogger();
|
OpenViduLogger.instance = new OpenViduLogger();
|
||||||
|
@ -18,31 +93,55 @@ export class OpenViduLogger {
|
||||||
if (!this.isProdMode) {
|
if (!this.isProdMode) {
|
||||||
this.LOG_FNS[0].apply(this.logger, arguments);
|
this.LOG_FNS[0].apply(this.logger, arguments);
|
||||||
}
|
}
|
||||||
|
if (this.isMonitoringLogEnabled()) {
|
||||||
|
JL().info(arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(...args: any[]) {
|
debug(...args: any[]) {
|
||||||
if (!this.isProdMode) {
|
if (!this.isProdMode) {
|
||||||
this.LOG_FNS[1].apply(this.logger, arguments);
|
this.LOG_FNS[1].apply(this.logger, arguments);
|
||||||
}
|
}
|
||||||
|
if (this.isMonitoringLogEnabled()) {
|
||||||
|
JL().debug(arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info(...args: any[]) {
|
info(...args: any[]) {
|
||||||
if (!this.isProdMode) {
|
if (!this.isProdMode) {
|
||||||
this.LOG_FNS[2].apply(this.logger, arguments);
|
this.LOG_FNS[2].apply(this.logger, arguments);
|
||||||
}
|
}
|
||||||
|
if (this.isMonitoringLogEnabled()) {
|
||||||
|
JL().info(arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(...args: any[]) {
|
warn(...args: any[]) {
|
||||||
if (!this.isProdMode) {
|
if (!this.isProdMode) {
|
||||||
this.LOG_FNS[3].apply(this.logger, arguments);
|
this.LOG_FNS[3].apply(this.logger, arguments);
|
||||||
}
|
}
|
||||||
|
if (this.isMonitoringLogEnabled()) {
|
||||||
|
JL().warn(arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
error(...args: any[]) {
|
error(...args: any[]) {
|
||||||
this.LOG_FNS[4].apply(this.logger, arguments);
|
this.LOG_FNS[4].apply(this.logger, arguments);
|
||||||
|
if (this.isMonitoringLogEnabled()) {
|
||||||
|
JL().error(arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enableProdMode(){
|
enableProdMode(){
|
||||||
this.isProdMode = true;
|
this.isProdMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableBrowserLogsMonitoring() {
|
||||||
|
this.isJSNLogEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isMonitoringLogEnabled() {
|
||||||
|
return this.isJSNLogEnabled && this.isJSNLogSetup;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
import io.openvidu.server.core.TokenRegister;
|
||||||
import org.bouncycastle.util.Arrays;
|
import org.bouncycastle.util.Arrays;
|
||||||
import org.kurento.jsonrpc.internal.server.config.JsonRpcConfiguration;
|
import org.kurento.jsonrpc.internal.server.config.JsonRpcConfiguration;
|
||||||
import org.kurento.jsonrpc.server.JsonRpcConfigurer;
|
import org.kurento.jsonrpc.server.JsonRpcConfigurer;
|
||||||
|
@ -169,6 +170,13 @@ public class OpenViduServer implements JsonRpcConfigurer {
|
||||||
return new TokenGenerator();
|
return new TokenGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
@DependsOn("openviduConfig")
|
||||||
|
public TokenRegister tokenRegister() {
|
||||||
|
return new TokenRegister();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
@DependsOn("openviduConfig")
|
@DependsOn("openviduConfig")
|
||||||
|
|
|
@ -56,16 +56,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
.csrf().disable().authorizeRequests()
|
.csrf().disable().authorizeRequests()
|
||||||
.antMatchers(HttpMethod.GET, RequestMappings.API + "/config/openvidu-publicurl").permitAll()
|
.antMatchers(HttpMethod.GET, RequestMappings.API + "/config/openvidu-publicurl").permitAll()
|
||||||
.antMatchers(HttpMethod.GET, RequestMappings.ACCEPT_CERTIFICATE).permitAll()
|
.antMatchers(HttpMethod.GET, RequestMappings.ACCEPT_CERTIFICATE).permitAll()
|
||||||
.antMatchers(RequestMappings.API + "/**").authenticated()
|
.antMatchers(RequestMappings.API + "/**").hasRole("ADMIN")
|
||||||
.antMatchers(HttpMethod.GET, RequestMappings.CDR + "/**").authenticated()
|
.antMatchers(HttpMethod.GET, RequestMappings.CDR + "/**").hasRole("ADMIN")
|
||||||
.antMatchers(HttpMethod.GET, RequestMappings.FRONTEND_CE + "/**").authenticated()
|
.antMatchers(HttpMethod.GET, RequestMappings.FRONTEND_CE + "/**").hasRole("ADMIN")
|
||||||
.antMatchers(HttpMethod.GET, RequestMappings.CUSTOM_LAYOUTS + "/**").authenticated();
|
.antMatchers(HttpMethod.GET, RequestMappings.CUSTOM_LAYOUTS + "/**").hasRole("ADMIN");
|
||||||
|
|
||||||
// Secure recordings depending on OPENVIDU_RECORDING_PUBLIC_ACCESS
|
// Secure recordings depending on OPENVIDU_RECORDING_PUBLIC_ACCESS
|
||||||
if (openviduConf.getOpenViduRecordingPublicAccess()) {
|
if (openviduConf.getOpenViduRecordingPublicAccess()) {
|
||||||
conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").permitAll();
|
conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").permitAll();
|
||||||
} else {
|
} else {
|
||||||
conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").authenticated();
|
conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").hasRole("ADMIN");
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.and().httpBasic();
|
conf.and().httpBasic();
|
||||||
|
|
|
@ -81,12 +81,16 @@ public abstract class SessionManager {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected TokenGenerator tokenGenerator;
|
protected TokenGenerator tokenGenerator;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected TokenRegister tokenRegister;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected QuarantineKiller quarantineKiller;
|
protected QuarantineKiller quarantineKiller;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected GeoLocationByIp geoLocationByIp;
|
protected GeoLocationByIp geoLocationByIp;
|
||||||
|
|
||||||
|
|
||||||
public FormatChecker formatChecker = new FormatChecker();
|
public FormatChecker formatChecker = new FormatChecker();
|
||||||
|
|
||||||
private UpdatableTimerTask sessionGarbageCollectorTimer;
|
private UpdatableTimerTask sessionGarbageCollectorTimer;
|
||||||
|
@ -327,6 +331,7 @@ public abstract class SessionManager {
|
||||||
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role,
|
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role,
|
||||||
kurentoOptions);
|
kurentoOptions);
|
||||||
session.storeToken(tokenObj);
|
session.storeToken(tokenObj);
|
||||||
|
tokenRegister.registerToken(session.getSessionId(), tokenObj);
|
||||||
return tokenObj;
|
return tokenObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,6 +340,7 @@ public abstract class SessionManager {
|
||||||
Token tokenObj = new Token(token, session.getSessionId(), connectionProperties,
|
Token tokenObj = new Token(token, session.getSessionId(), connectionProperties,
|
||||||
this.openviduConfig.isTurnadminAvailable() ? this.coturnCredentialsService.createUser() : null);
|
this.openviduConfig.isTurnadminAvailable() ? this.coturnCredentialsService.createUser() : null);
|
||||||
session.storeToken(tokenObj);
|
session.storeToken(tokenObj);
|
||||||
|
tokenRegister.registerToken(session.getSessionId(), tokenObj);
|
||||||
return tokenObj;
|
return tokenObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,6 +629,7 @@ public abstract class SessionManager {
|
||||||
sessionidParticipantpublicidParticipant.remove(sessionId);
|
sessionidParticipantpublicidParticipant.remove(sessionId);
|
||||||
sessionidFinalUsers.remove(sessionId);
|
sessionidFinalUsers.remove(sessionId);
|
||||||
sessionidAccumulatedRecordings.remove(sessionId);
|
sessionidAccumulatedRecordings.remove(sessionId);
|
||||||
|
tokenRegister.deregisterTokens(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCollections(String sessionId) {
|
private void initializeCollections(String sessionId) {
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package io.openvidu.server.core;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service class represents all the tokens currently registered by an active sessions
|
||||||
|
* Each time a token is created, it is registered by {@link SessionManager} using {@link #registerToken(String, Token)}
|
||||||
|
* All registered tokens will be present until {@link SessionManager} calls the method {@link #deregisterTokens(String)}
|
||||||
|
*
|
||||||
|
* The purpose of this service is to know when a token was registered into a session by using
|
||||||
|
* public method {@link #isTokenRegistered(String)}
|
||||||
|
*/
|
||||||
|
public class TokenRegister {
|
||||||
|
|
||||||
|
private ConcurrentHashMap<String, Token> tokensRegistered = new ConcurrentHashMap<>();
|
||||||
|
private ConcurrentHashMap<String, ConcurrentHashMap<String, Token>> tokensRegisteredBySession = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a token of an specific active session
|
||||||
|
* @param sessionId Id of the sessions where the token is generated
|
||||||
|
* @param token Token to register
|
||||||
|
*/
|
||||||
|
protected synchronized void registerToken(String sessionId, Token token) {
|
||||||
|
this.tokensRegisteredBySession.putIfAbsent(sessionId, new ConcurrentHashMap<>());
|
||||||
|
ConcurrentHashMap<String, Token> registeredTokensInSession = this.tokensRegisteredBySession.get(sessionId);
|
||||||
|
this.tokensRegistered.put(token.getToken(), token);
|
||||||
|
registeredTokensInSession.put(token.getToken(), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregister all tokens of an specific session which is not active
|
||||||
|
* @param sessionId Id of the session which is no longer active
|
||||||
|
*/
|
||||||
|
protected synchronized void deregisterTokens(String sessionId) {
|
||||||
|
if (tokensRegisteredBySession.containsKey(sessionId)) {
|
||||||
|
for(Map.Entry<String, Token> tokenRegisteredInSession: tokensRegistered.entrySet()) {
|
||||||
|
tokensRegistered.remove(tokenRegisteredInSession.getKey());
|
||||||
|
}
|
||||||
|
tokensRegisteredBySession.remove(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current token string was registered in an active session
|
||||||
|
* @param token Token string to check if it is registered
|
||||||
|
* @return <code>true</code> if token was registered. <code>false</code> otherwise
|
||||||
|
*/
|
||||||
|
public boolean isTokenRegistered(String token) {
|
||||||
|
return this.tokensRegistered.containsKey(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get registered token from token string
|
||||||
|
* @param tokenKey string key which represents the token
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Token getRegisteredToken(String tokenKey) {
|
||||||
|
Token token = this.tokensRegistered.get(tokenKey);
|
||||||
|
if (token != null) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -120,25 +120,25 @@ public class ApiRestPathRewriteFilter implements Filter {
|
||||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry conf,
|
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry conf,
|
||||||
OpenviduConfig openviduConf) throws Exception {
|
OpenviduConfig openviduConf) throws Exception {
|
||||||
|
|
||||||
conf.antMatchers("/api/**").authenticated()
|
conf.antMatchers("/api/**").hasRole("ADMIN")
|
||||||
// /config
|
// /config
|
||||||
.antMatchers(HttpMethod.GET, "/config/openvidu-publicurl").permitAll()
|
.antMatchers(HttpMethod.GET, "/config/openvidu-publicurl").permitAll()
|
||||||
.antMatchers(HttpMethod.GET, "/config/**").authenticated()
|
.antMatchers(HttpMethod.GET, "/config/**").hasRole("ADMIN")
|
||||||
// /cdr
|
// /cdr
|
||||||
.antMatchers(HttpMethod.GET, "/cdr/**").authenticated()
|
.antMatchers(HttpMethod.GET, "/cdr/**").hasRole("ADMIN")
|
||||||
// /accept-certificate
|
// /accept-certificate
|
||||||
.antMatchers(HttpMethod.GET, "/accept-certificate").permitAll()
|
.antMatchers(HttpMethod.GET, "/accept-certificate").permitAll()
|
||||||
// Dashboard
|
// Dashboard
|
||||||
.antMatchers(HttpMethod.GET, "/dashboard/**").authenticated();
|
.antMatchers(HttpMethod.GET, "/dashboard/**").hasRole("ADMIN");
|
||||||
|
|
||||||
// Security for recording layouts
|
// Security for recording layouts
|
||||||
conf.antMatchers("/layouts/**").authenticated();
|
conf.antMatchers("/layouts/**").hasRole("ADMIN");
|
||||||
|
|
||||||
// Security for recorded video files
|
// Security for recorded video files
|
||||||
if (openviduConf.getOpenViduRecordingPublicAccess()) {
|
if (openviduConf.getOpenViduRecordingPublicAccess()) {
|
||||||
conf = conf.antMatchers("/recordings/**").permitAll();
|
conf = conf.antMatchers("/recordings/**").permitAll();
|
||||||
} else {
|
} else {
|
||||||
conf = conf.antMatchers("/recordings/**").authenticated();
|
conf = conf.antMatchers("/recordings/**").hasRole("ADMIN");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ server.ssl.key-store=classpath:openvidu-selfsigned.jks
|
||||||
server.ssl.key-store-password=openvidu
|
server.ssl.key-store-password=openvidu
|
||||||
server.ssl.key-store-type=JKS
|
server.ssl.key-store-type=JKS
|
||||||
server.ssl.key-alias=openvidu-selfsigned
|
server.ssl.key-alias=openvidu-selfsigned
|
||||||
|
server.servlet.session.cookie.name=OVJSESSIONID
|
||||||
|
|
||||||
logging.level.root=info
|
logging.level.root=info
|
||||||
spring.main.allow-bean-definition-overriding=true
|
spring.main.allow-bean-definition-overriding=true
|
||||||
|
|
Loading…
Reference in New Issue