diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index dcfbcb98..a0526d08 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -1322,6 +1322,10 @@ export class Session extends EventDispatcher { reject(error); } else { + // Configure JSNLogs + OpenViduLogger.configureJSNLog(this.openvidu, this.sessionId, response.id, token); + + // Process join room response this.processJoinRoomResponse(response); // 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.httpUri = 'https://' + url.host; - } else { logger.error('Token "' + token + '" is not valid') } diff --git a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js index 59305201..940a4107 100644 --- a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js +++ b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js @@ -16,7 +16,8 @@ "use strict"; -var Logger = console; +var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger; +var Logger = OpenViduLogger.getInstance(); var MAX_RETRIES = 2000; // Forever... var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times... diff --git a/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts b/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts index 3f4fbc01..693eae9e 100644 --- a/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts +++ b/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts @@ -1,12 +1,87 @@ +import {JL} from 'jsnlog' +import {OpenVidu} from "../../OpenVidu/OpenVidu"; + export class 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 LOG_FNS = [this.logger.log, this.logger.debug, this.logger.info, this.logger.warn, this.logger.error]; private isProdMode = false; + private isJSNLogEnabled = true; + private isJSNLogSetup = false; + 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 { if(!OpenViduLogger.instance){ OpenViduLogger.instance = new OpenViduLogger(); @@ -18,31 +93,55 @@ export class OpenViduLogger { if (!this.isProdMode) { this.LOG_FNS[0].apply(this.logger, arguments); } + if (this.isMonitoringLogEnabled()) { + JL().info(arguments); + } } debug(...args: any[]) { if (!this.isProdMode) { this.LOG_FNS[1].apply(this.logger, arguments); } + if (this.isMonitoringLogEnabled()) { + JL().debug(arguments); + } } info(...args: any[]) { if (!this.isProdMode) { this.LOG_FNS[2].apply(this.logger, arguments); } + if (this.isMonitoringLogEnabled()) { + JL().info(arguments); + } } warn(...args: any[]) { if (!this.isProdMode) { this.LOG_FNS[3].apply(this.logger, arguments); } + if (this.isMonitoringLogEnabled()) { + JL().warn(arguments); + } } error(...args: any[]) { this.LOG_FNS[4].apply(this.logger, arguments); + if (this.isMonitoringLogEnabled()) { + JL().error(arguments); + } } enableProdMode(){ this.isProdMode = true; } + + disableBrowserLogsMonitoring() { + this.isJSNLogEnabled = false; + } + + private isMonitoringLogEnabled() { + return this.isJSNLogEnabled && this.isJSNLogSetup; + } + } \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java index cabacc17..514d2d6c 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java +++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Semaphore; +import io.openvidu.server.core.TokenRegister; import org.bouncycastle.util.Arrays; import org.kurento.jsonrpc.internal.server.config.JsonRpcConfiguration; import org.kurento.jsonrpc.server.JsonRpcConfigurer; @@ -169,6 +170,13 @@ public class OpenViduServer implements JsonRpcConfigurer { return new TokenGenerator(); } + @Bean + @ConditionalOnMissingBean + @DependsOn("openviduConfig") + public TokenRegister tokenRegister() { + return new TokenRegister(); + } + @Bean @ConditionalOnMissingBean @DependsOn("openviduConfig") diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java index c06b1a6e..57719532 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java @@ -56,16 +56,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .csrf().disable().authorizeRequests() .antMatchers(HttpMethod.GET, RequestMappings.API + "/config/openvidu-publicurl").permitAll() .antMatchers(HttpMethod.GET, RequestMappings.ACCEPT_CERTIFICATE).permitAll() - .antMatchers(RequestMappings.API + "/**").authenticated() - .antMatchers(HttpMethod.GET, RequestMappings.CDR + "/**").authenticated() - .antMatchers(HttpMethod.GET, RequestMappings.FRONTEND_CE + "/**").authenticated() - .antMatchers(HttpMethod.GET, RequestMappings.CUSTOM_LAYOUTS + "/**").authenticated(); + .antMatchers(RequestMappings.API + "/**").hasRole("ADMIN") + .antMatchers(HttpMethod.GET, RequestMappings.CDR + "/**").hasRole("ADMIN") + .antMatchers(HttpMethod.GET, RequestMappings.FRONTEND_CE + "/**").hasRole("ADMIN") + .antMatchers(HttpMethod.GET, RequestMappings.CUSTOM_LAYOUTS + "/**").hasRole("ADMIN"); // Secure recordings depending on OPENVIDU_RECORDING_PUBLIC_ACCESS if (openviduConf.getOpenViduRecordingPublicAccess()) { conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").permitAll(); } else { - conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").authenticated(); + conf = conf.antMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").hasRole("ADMIN"); } conf.and().httpBasic(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java index 844de1db..eba563b7 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java @@ -81,12 +81,16 @@ public abstract class SessionManager { @Autowired protected TokenGenerator tokenGenerator; + @Autowired + protected TokenRegister tokenRegister; + @Autowired protected QuarantineKiller quarantineKiller; @Autowired protected GeoLocationByIp geoLocationByIp; + public FormatChecker formatChecker = new FormatChecker(); private UpdatableTimerTask sessionGarbageCollectorTimer; @@ -327,6 +331,7 @@ public abstract class SessionManager { Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role, kurentoOptions); session.storeToken(tokenObj); + tokenRegister.registerToken(session.getSessionId(), tokenObj); return tokenObj; } @@ -335,6 +340,7 @@ public abstract class SessionManager { Token tokenObj = new Token(token, session.getSessionId(), connectionProperties, this.openviduConfig.isTurnadminAvailable() ? this.coturnCredentialsService.createUser() : null); session.storeToken(tokenObj); + tokenRegister.registerToken(session.getSessionId(), tokenObj); return tokenObj; } @@ -623,6 +629,7 @@ public abstract class SessionManager { sessionidParticipantpublicidParticipant.remove(sessionId); sessionidFinalUsers.remove(sessionId); sessionidAccumulatedRecordings.remove(sessionId); + tokenRegister.deregisterTokens(sessionId); } private void initializeCollections(String sessionId) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/TokenRegister.java b/openvidu-server/src/main/java/io/openvidu/server/core/TokenRegister.java new file mode 100644 index 00000000..c31b9f71 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/core/TokenRegister.java @@ -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 tokensRegistered = new ConcurrentHashMap<>(); + private ConcurrentHashMap> 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 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 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 true if token was registered. false 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; + } + + +} diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/ApiRestPathRewriteFilter.java b/openvidu-server/src/main/java/io/openvidu/server/rest/ApiRestPathRewriteFilter.java index 2abe443b..606f8ef4 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/ApiRestPathRewriteFilter.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/ApiRestPathRewriteFilter.java @@ -120,25 +120,25 @@ public class ApiRestPathRewriteFilter implements Filter { ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry conf, OpenviduConfig openviduConf) throws Exception { - conf.antMatchers("/api/**").authenticated() + conf.antMatchers("/api/**").hasRole("ADMIN") // /config .antMatchers(HttpMethod.GET, "/config/openvidu-publicurl").permitAll() - .antMatchers(HttpMethod.GET, "/config/**").authenticated() + .antMatchers(HttpMethod.GET, "/config/**").hasRole("ADMIN") // /cdr - .antMatchers(HttpMethod.GET, "/cdr/**").authenticated() + .antMatchers(HttpMethod.GET, "/cdr/**").hasRole("ADMIN") // /accept-certificate .antMatchers(HttpMethod.GET, "/accept-certificate").permitAll() // Dashboard - .antMatchers(HttpMethod.GET, "/dashboard/**").authenticated(); + .antMatchers(HttpMethod.GET, "/dashboard/**").hasRole("ADMIN"); // Security for recording layouts - conf.antMatchers("/layouts/**").authenticated(); + conf.antMatchers("/layouts/**").hasRole("ADMIN"); // Security for recorded video files if (openviduConf.getOpenViduRecordingPublicAccess()) { conf = conf.antMatchers("/recordings/**").permitAll(); } else { - conf = conf.antMatchers("/recordings/**").authenticated(); + conf = conf.antMatchers("/recordings/**").hasRole("ADMIN"); } } diff --git a/openvidu-server/src/main/resources/application.properties b/openvidu-server/src/main/resources/application.properties index 92291fc5..6b19ea72 100644 --- a/openvidu-server/src/main/resources/application.properties +++ b/openvidu-server/src/main/resources/application.properties @@ -4,6 +4,7 @@ server.ssl.key-store=classpath:openvidu-selfsigned.jks server.ssl.key-store-password=openvidu server.ssl.key-store-type=JKS server.ssl.key-alias=openvidu-selfsigned +server.servlet.session.cookie.name=OVJSESSIONID logging.level.root=info spring.main.allow-bean-definition-overriding=true