openvidu-pro: Adapt openvidu-browser and openvidu-server ce to send browser logs related with openvidu-browser in OpenVidu Pro

pull/621/head
cruizba 2021-03-31 17:12:37 +02:00
parent da3fb64073
commit 5841d15a86
9 changed files with 199 additions and 13 deletions

View File

@ -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')
} }

View File

@ -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...

View File

@ -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;
}
} }

View File

@ -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")

View File

@ -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();

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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");
} }
} }

View File

@ -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