Skip to content

Commit

Permalink
Merge pull request #909 from virtualcell/905-webauth-verifiers
Browse files Browse the repository at this point in the history
web auth verifier cleanup
  • Loading branch information
jcschaff authored Jun 15, 2023
2 parents 27d3af5 + b92871e commit 899f888
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 209 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
package org.vcell.rest;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;

import cbit.vcell.modeldb.AdminDBTopLevel;
import cbit.vcell.modeldb.ApiAccessToken;
import cbit.vcell.modeldb.ApiAccessToken.AccessTokenStatus;
import cbit.vcell.modeldb.ApiClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jose4j.jwt.MalformedClaimException;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.ext.crypto.CookieAuthenticator;
import org.restlet.security.Verifier;
import org.vcell.auth.JWTUtils;
import org.vcell.rest.users.UnverifiedUser;
import org.vcell.util.DataAccessException;
Expand All @@ -25,14 +16,14 @@
import org.vcell.util.document.UserLoginInfo;
import org.vcell.util.document.UserLoginInfo.DigestedPassword;

import cbit.vcell.modeldb.AdminDBTopLevel;
import cbit.vcell.modeldb.ApiAccessToken;
import cbit.vcell.modeldb.ApiAccessToken.AccessTokenStatus;
import cbit.vcell.modeldb.ApiClient;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;


public class UserVerifier implements Verifier {
private final static Logger lg = LogManager.getLogger(UserVerifier.class);
public class UserService {
private final static Logger lg = LogManager.getLogger(UserService.class);

private static class AuthenticationInfo {
final User user;
Expand All @@ -56,7 +47,7 @@ public enum AuthenticationStatus {
private long lastQueryTimestampMS = 0;
private final static long MIN_QUERY_TIME_MS = 1000*5; // 5 seconds

public UserVerifier(AdminDBTopLevel adminDbTopLevel){
public UserService(AdminDBTopLevel adminDbTopLevel){
this.adminDbTopLevel = adminDbTopLevel;
}

Expand Down Expand Up @@ -92,12 +83,22 @@ public ApiAccessToken generateApiAccessToken(KeyValue apiClientKey, User user) t
return adminDbTopLevel.generateApiAccessToken(apiClientKey, user, getNewExpireDate(), true);
}

public ApiAccessToken getApiAccessToken(String accessToken) throws SQLException, DataAccessException{
ApiAccessToken apiAccessToken = this.accessTokenMap.get(accessToken);
public ApiAccessToken getApiAccessToken(String jwtToken) throws SQLException, DataAccessException{
try {
if (!JWTUtils.verifyJWS(jwtToken)){
lg.warn("token was not valid");
return null;
}
} catch (MalformedClaimException e) {
lg.error("token was not valid", e);
return null;
}

ApiAccessToken apiAccessToken = this.accessTokenMap.get(jwtToken);
if (apiAccessToken==null){
apiAccessToken = adminDbTopLevel.getApiAccessToken(accessToken, true);
apiAccessToken = adminDbTopLevel.getApiAccessToken(jwtToken, true);
if (apiAccessToken!=null){
accessTokenMap.put(accessToken, apiAccessToken);
accessTokenMap.put(jwtToken, apiAccessToken);
}
}
return apiAccessToken;
Expand Down Expand Up @@ -128,73 +129,6 @@ public ApiClient getApiClient(String clientId) throws SQLException, DataAccessEx
throw new RuntimeException("invalid client");
}

@Override
public int verify(Request request, Response response) {
ChallengeResponse challengeResponse = request.getChallengeResponse();
AuthenticationStatus result = verify(challengeResponse);

Context.getCurrent().getLogger().log(Level.FINE,"UserVerifier.verify(request,response) - returning "+result+", request='"+request+"'");

switch (result){
case invalid:{
request.getCookies().removeAll("org.vcell.auth");
response.getCookieSettings().removeAll("org.vcell.auth");
return RESULT_INVALID;
}
case stale:{
request.getCookies().removeAll("org.vcell.auth");
response.getCookieSettings().removeAll("org.vcell.auth");
return RESULT_STALE;
}
case missing:{
return RESULT_MISSING;
}
case valid:{
return RESULT_VALID;
}
default: {
return RESULT_UNKNOWN;
}
}
}

public AuthenticationStatus verify(ChallengeResponse challengeResponse) {
if (challengeResponse != null && challengeResponse.getScheme().equals(ChallengeScheme.HTTP_OAUTH_BEARER) && challengeResponse.getRawValue() != null) {
try {
ApiAccessToken accessToken = getApiAccessToken(challengeResponse.getRawValue());
if (accessToken == null) {
return AuthenticationStatus.invalid;
} else if (accessToken.isExpired()) {
return AuthenticationStatus.stale;
} else if (accessToken.getStatus() == AccessTokenStatus.invalidated) {
return AuthenticationStatus.invalid;
} else {
return AuthenticationStatus.valid;
}
} catch (Exception e) {
lg.error(e.getMessage(), e);
return AuthenticationStatus.invalid;
}
} else if (challengeResponse != null && challengeResponse.getScheme().equals(ChallengeScheme.HTTP_COOKIE) && challengeResponse.getSecret() != null) {
String token = new String(challengeResponse.getSecret());
try {
boolean valid = JWTUtils.verifyJWS(token);
if (valid) {
return AuthenticationStatus.valid;
} else {
return AuthenticationStatus.invalid;
}
} catch (MalformedClaimException e) {
lg.error("token was not valid", e);
return AuthenticationStatus.invalid;
}
} else if (challengeResponse == null) {
return AuthenticationStatus.missing;
} else {
return AuthenticationStatus.invalid;
}
}

public void addUnverifiedUser(UnverifiedUser unverifiedUser){
String userid = unverifiedUser.submittedUserInfo.userid;

Expand Down
71 changes: 26 additions & 45 deletions vcell-api/src/main/java/org/vcell/rest/VCellApiApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import org.restlet.resource.ResourceException;
import org.restlet.routing.Router;
import org.restlet.security.ChallengeAuthenticator;
import org.vcell.rest.UserVerifier.AuthenticationStatus;
import org.vcell.rest.admin.AdminJobsRestlet;
import org.vcell.rest.admin.AdminService;
import org.vcell.rest.auth.AuthenticationTokenRestlet;
import org.vcell.rest.auth.TokenBasedVerifier;
import org.vcell.rest.auth.BearerTokenVerifier;
import org.vcell.rest.auth.CookieVerifier;
import org.vcell.rest.events.EventsRestlet;
import org.vcell.rest.events.RestEventService;
import org.vcell.rest.health.HealthRestlet;
Expand All @@ -29,12 +29,10 @@
import org.vcell.rest.rpc.RpcService;
import org.vcell.rest.server.*;
import org.vcell.rest.users.*;
import org.vcell.util.DataAccessException;
import org.vcell.util.document.KeyValue;
import org.vcell.util.document.User;

import java.io.File;
import java.sql.SQLException;
import java.util.logging.Level;

public class VCellApiApplication extends WadlApplication {
Expand Down Expand Up @@ -154,7 +152,10 @@ public enum AuthenticationPolicy {


private RestDatabaseService restDatabaseService = null;
private UserVerifier userVerifier = null;
private UserService userService = null;

private BearerTokenVerifier bearerTokenVerifier = null;
private CookieVerifier cookieVerifier = null;
private Configuration templateConfiguration = null;
private File javascriptDir = null;
private RpcService rpcService = null;
Expand Down Expand Up @@ -186,7 +187,7 @@ protected Representation createHtmlRepresentation(ApplicationInfo applicationInf

public VCellApiApplication(
RestDatabaseService restDatabaseService,
UserVerifier userVerifier,
UserService userService,
RpcService rpcService,
RestEventService restEventService,
AdminService adminService,
Expand All @@ -202,7 +203,7 @@ public VCellApiApplication(
this.javascriptDir = javascriptDir;
this.restDatabaseService = restDatabaseService;
this.adminService = adminService;
this.userVerifier = userVerifier;
this.userService = userService;
this.rpcService = rpcService;
this.restEventService = restEventService;
this.templateConfiguration = templateConfiguration;
Expand Down Expand Up @@ -327,16 +328,17 @@ public void handle(Request request, Response response) {

// Attach an auth bearer guard to secure access to user parts of the api via
boolean bAuthOptional = true;
ChallengeAuthenticator bearerTokenGuard = new ChallengeAuthenticator(
ChallengeAuthenticator bearerTokenAuthenticator = new ChallengeAuthenticator(
getContext(), bAuthOptional, ChallengeScheme.HTTP_OAUTH_BEARER, "testRealm");
TokenBasedVerifier verifier = new TokenBasedVerifier();
bearerTokenGuard.setMultiAuthenticating(false); // if it is already authenticated via cookies, then don't authenticate again
bearerTokenGuard.setVerifier(verifier);
bearerTokenVerifier = new BearerTokenVerifier(userService);
bearerTokenAuthenticator.setMultiAuthenticating(false); // if it is already authenticated via cookies, then don't authenticate again
bearerTokenAuthenticator.setVerifier(bearerTokenVerifier);

// Attach a cookie based Basic auth guard to secure access to browser access.
boolean bCookieOptional = true;
cookieVerifier = new CookieVerifier(userService);
final VCellCookieAuthenticator cookieAuthenticator = new VCellCookieAuthenticator(
this, bCookieOptional, "My cookie realm", "MyExtraSecretKey".getBytes());
userService, cookieVerifier, getContext(), bCookieOptional, "My cookie realm", "MyExtraSecretKey".getBytes());
cookieAuthenticator.setMultiAuthenticating(false);
cookieAuthenticator.setLoginPath("/"+LOGIN);
cookieAuthenticator.setLogoutPath("/"+LOGOUT);
Expand All @@ -345,11 +347,10 @@ public void handle(Request request, Response response) {
cookieAuthenticator.setIdentifierFormName(IDENTIFIER_FORMNAME);
cookieAuthenticator.setSecretFormName(SECRET_FORMNAME);
cookieAuthenticator.setRedirectQueryName(REDIRECTURL_FORMNAME);
cookieAuthenticator.setVerifier(userVerifier);
cookieAuthenticator.setMaxCookieAge(15*60); // 15 minutes (in units of seconds).

bearerTokenGuard.setNext(rootRouter);
cookieAuthenticator.setNext(bearerTokenGuard);
bearerTokenAuthenticator.setNext(rootRouter);
cookieAuthenticator.setNext(bearerTokenAuthenticator);
return cookieAuthenticator;
}

Expand All @@ -361,17 +362,20 @@ public Configuration getTemplateConfiguration() {
return this.templateConfiguration;
}

public UserVerifier getUserVerifier(){
return userVerifier;
public UserService getUserService(){
return userService;
}

public AdminService getAdminService() {
return this.adminService;
}

public User getVCellUser(ChallengeResponse response, AuthenticationPolicy authPolicy) {
public User getVCellUser(ChallengeResponse response, AuthenticationPolicy authPolicy) throws ResourceException {
try {
ApiAccessToken accessToken = getApiAccessToken(response);
ApiAccessToken accessToken = bearerTokenVerifier.getApiAccessToken(response);
if (accessToken == null){
accessToken = cookieVerifier.getApiAccessToken(response);
}
if (accessToken!=null){
if (accessToken.isExpired()){
if (authPolicy == AuthenticationPolicy.ignoreInvalidCredentials){
Expand All @@ -392,17 +396,11 @@ public User getVCellUser(ChallengeResponse response, AuthenticationPolicy authPo
return accessToken.getUser();
}
}else{ // accessToken is null
AuthenticationStatus authStatus = userVerifier.verify(response);
if (authStatus==AuthenticationStatus.missing){
getLogger().log(Level.FINE,"VCellApiApplication.getVCellUser(response) - ApiAccessToken not provided ... returning user = null");
if (authPolicy == AuthenticationPolicy.ignoreInvalidCredentials){
getLogger().log(Level.INFO,"VCellApiApplication.getVCellUser(response) - ApiAccessToken not found in database ... returning user = null");
return null;
}else{
if (authPolicy == AuthenticationPolicy.ignoreInvalidCredentials){
getLogger().log(Level.INFO,"VCellApiApplication.getVCellUser(response) - ApiAccessToken not found in database ... returning user = null");
return null;
}else{
throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED, "access_token invalid");
}
throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED, "access_token invalid");
}
}
}catch (Exception e){
Expand All @@ -414,21 +412,4 @@ public User getVCellUser(ChallengeResponse response, AuthenticationPolicy authPo
}
}
}

ApiAccessToken getApiAccessToken(ChallengeResponse response) throws SQLException, DataAccessException{
if (response==null){
getLogger().log(Level.INFO,"VCellApiApplication.getApiAccessToken(response) - response was null");
return null;
}else if (response.getSecret() != null) {
ApiAccessToken accessToken = userVerifier.getApiAccessToken(new String(response.getSecret()));
return accessToken;
}else if (response.getRawValue() != null) {
ApiAccessToken accessToken = userVerifier.getApiAccessToken(new String(response.getRawValue()));
return accessToken;
}else{
getLogger().log(Level.INFO,"VCellApiApplication.getApiAccessToken(response) - response.getSecret() and response.getRawValue() were both null");
return null;
}
}

}
4 changes: 2 additions & 2 deletions vcell-api/src/main/java/org/vcell/rest/VCellApiMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void onException(Exception e) {
RestEventService restEventService = new RestEventService(vcMessagingService_int);

lg.trace("use verifier (next)");
UserVerifier userVerifier = new UserVerifier(adminDbTopLevel);
UserService userService = new UserService(adminDbTopLevel);

lg.trace("mongo (next)");
VCMongoMessage.enabled=true;
Expand Down Expand Up @@ -232,7 +232,7 @@ public void onException(Exception e) {
testUserInfo.userid, testUserInfo.digestedPassword0);
AdminService adminService = new AdminService(adminDbTopLevel, databaseServerImpl);
RpcService rpcService = new RpcService(vcMessagingService_int);
WadlApplication app = new VCellApiApplication(restDatabaseService, userVerifier, rpcService, restEventService, adminService, templateConfiguration, healthService, javascriptDir);
WadlApplication app = new VCellApiApplication(restDatabaseService, userService, rpcService, restEventService, adminService, templateConfiguration, healthService, javascriptDir);
lg.trace("attach app");
component.getDefaultHost().attach(app);

Expand Down
Loading

0 comments on commit 899f888

Please sign in to comment.