/* * Copyright 2013-2017 Erudika. https://erudika.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For issues and patches go to: https://github.com/erudika */ package com.erudika.para.security; import com.erudika.para.core.App; import com.erudika.para.core.User; import com.erudika.para.utils.Config; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.JWSVerifier; import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.MACVerifier; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import java.text.ParseException; import java.util.Date; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; /** * Utility class with helper methods for authentication. * @author Alex Bogdanovski [alex@erudika.com] */ public final class SecurityUtils { private static final Logger logger = LoggerFactory.getLogger(SecurityUtils.class); private SecurityUtils() { } /** * Extracts a User object from the security context. * @return an authenticated user or null if a user is not authenticated */ public static User getAuthenticatedUser() { return getAuthenticatedUser(SecurityContextHolder.getContext().getAuthentication()); } /** * Extracts a User object from the security context. * @param auth the authentication object * @return an authenticated user or null if a user is not authenticated */ public static User getAuthenticatedUser(Authentication auth) { User user = null; if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof AuthenticatedUserDetails) { user = ((AuthenticatedUserDetails) auth.getPrincipal()).getUser(); } return user; } /** * Extracts a App object from the security context. * @return an authenticated app or null if a app is not authenticated */ public static App getAuthenticatedApp() { App app = null; if (SecurityContextHolder.getContext().getAuthentication() != null) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth.isAuthenticated() && auth.getPrincipal() instanceof App) { app = (App) auth.getPrincipal(); } } return app; } /** * Clears the session. Deletes cookies and clears the security context. * @param req HTTP request */ public static void clearSession(HttpServletRequest req) { SecurityContextHolder.clearContext(); if (req != null) { HttpSession session = req.getSession(false); if (session != null) { session.invalidate(); } } } /** * Validates a JWT token. * @param secret secret used for generating the token * @param jwt token to validate * @return true if token is valid */ public static boolean isValidJWToken(String secret, SignedJWT jwt) { try { if (secret != null && jwt != null) { JWSVerifier verifier = new MACVerifier(secret); if (jwt.verify(verifier)) { Date referenceTime = new Date(); JWTClaimsSet claims = jwt.getJWTClaimsSet(); Date expirationTime = claims.getExpirationTime(); Date notBeforeTime = claims.getNotBeforeTime(); boolean expired = expirationTime == null || expirationTime.before(referenceTime); boolean notYetValid = notBeforeTime == null || notBeforeTime.after(referenceTime); return !(expired || notYetValid); } } } catch (JOSEException e) { logger.warn(null, e); } catch (ParseException ex) { logger.warn(null, ex); } return false; } /** * Generates a new "super" JWT token for apps. * @param app the app object * @return a new JWT or null */ public static SignedJWT generateSuperJWToken(App app) { return generateJWToken(null, app); } /** * Generates a new JWT token. * @param user a User object belonging to the app * @param app the app object * @return a new JWT or null */ public static SignedJWT generateJWToken(User user, App app) { if (app != null) { try { Date now = new Date(); JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder(); String userSecret = ""; claimsSet.issueTime(now); claimsSet.expirationTime(new Date(now.getTime() + (app.getTokenValiditySec() * 1000))); claimsSet.notBeforeTime(now); claimsSet.claim("refresh", getNextRefresh(app.getTokenValiditySec())); claimsSet.claim(Config._APPID, app.getId()); if (user != null) { claimsSet.subject(user.getId()); userSecret = user.getTokenSecret(); } JWSSigner signer = new MACSigner(app.getSecret() + userSecret); SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet.build()); signedJWT.sign(signer); return signedJWT; } catch (JOSEException e) { logger.warn("Unable to sign JWT: {}.", e.getMessage()); } } return null; } /** * Decides when the next token refresh should be. * @param tokenValiditySec token validity period * @return a refresh timestamp to be used by API clients */ private static long getNextRefresh(long tokenValiditySec) { long interval = Config.JWT_REFRESH_INTERVAL_SEC; // estimate when the next token refresh should be // usually every hour, or halfway until the time it expires if (tokenValiditySec < (2 * interval)) { interval = (tokenValiditySec / 2); } return System.currentTimeMillis() + (interval * 1000); } /** * Return the OAuth app ID and secret key for a given app by reading the app settings, or the config file. * @param app the app in which to look for these keys * @param prefix a service prefix: "fb" for facebook, "tw" for twitter etc. See {@link Config} * @return an array ["app_id", "secret_key"] or ["", ""] */ public static String[] getOAuthKeysForApp(App app, String prefix) { prefix = StringUtils.removeEnd(prefix + "", Config.SEPARATOR); String appIdKey = prefix + "_app_id"; String secretKey = prefix + "_secret"; String[] keys = new String[]{"", ""}; if (app != null) { Map<String, Object> settings = app.getSettings(); if (settings.containsKey(appIdKey) && settings.containsKey(secretKey)) { keys[0] = settings.get(appIdKey) + ""; keys[1] = settings.get(secretKey) + ""; } else if (app.isRootApp()) { keys[0] = Config.getConfigParam(appIdKey, ""); keys[1] = Config.getConfigParam(secretKey, ""); } } return keys; } }