diff --git a/ModuleConfig.cfc b/ModuleConfig.cfc index 4e5caea..63bf93b 100644 --- a/ModuleConfig.cfc +++ b/ModuleConfig.cfc @@ -32,6 +32,7 @@ component { */ function configure(){ settings = { + enableCBAuthIntegration: false, errorRedirect: "", successRedirect: "", providers : [ @@ -45,6 +46,13 @@ component { // } ] }; + + interceptorSettings = { + customInterceptionPoints = [ + "CBSSOMissingProvider", + "CBSSOAuthorization" + ] + }; }; @@ -54,6 +62,16 @@ component { function onLoad(){ // Register all app disks wirebox.getInstance( "ProviderService@cbsso" ).registerProviders(); + + if ( settings.enableCBAuthIntegration ) { + controller + .getInterceptorService() + .registerInterceptor( + interceptorClass = "cbsso.interceptors.cbAuth", + interceptorProperties = settings, + interceptorName = "cbsso@global" + ); + } } /** diff --git a/handlers/Auth.cfc b/handlers/Auth.cfc index ef2a040..544175f 100644 --- a/handlers/Auth.cfc +++ b/handlers/Auth.cfc @@ -6,7 +6,9 @@ component { var provider = ProviderService.get( event.getValue( "providerName", "" ) ); if( isNull( provider ) ){ - // TODO add interception point to handle missing provider + announce( "CBSSOMissingProvider", { + "providerName": event.getValue( "providerName", "" ) + } ); relocate( moduleSettings.errorRedirect ); } @@ -17,18 +19,20 @@ component { var provider = ProviderService.get( event.getValue( "providerName", "" ) ); if( isNull( provider ) ){ - // TODO add interception point to handle missing provider + announce( "CBSSOMissingProvider", { + "providerName": event.getValue( "providerName", "" ) + } ); + relocate( moduleSettings.errorRedirect ); } var ssoAuthorizationEvent = provider.processAuthorizationEvent( event ); - announce( "CBSSOOnAuthorization", { + announce( "CBSSOAuthorization", { "provider": provider, "ssoAuthorizationEvent": ssoAuthorizationEvent } ); - - // TODO this should probably be a module setting + relocate( moduleSettings.successRedirect ); } diff --git a/interceptors/cbAuth.cfc b/interceptors/cbAuth.cfc new file mode 100644 index 0000000..819f52e --- /dev/null +++ b/interceptors/cbAuth.cfc @@ -0,0 +1,22 @@ +component { + property name="moduleSettings" inject="coldbox:moduleSettings:cbsso"; + + public void function CBSSOAuthorization( event, data ){ + + var authService = getInstance( "authenticationService@cbauth" ); + var userService = authService.getUserService(); + + var user = userService.findBySSO( data.ssoAuthorizationEvent, data.provider ); + + if( isNull( user ) ){ + user = userService.createFromSSO( data.ssoAuthorizationEvent, data.provider ); + } + else { + userService.updateFromSSO( user, data.ssoAuthorizationEvent, data.provider ); + } + + authService.login( user ); + + relocate( moduleSettings.successRedirect ); + } +} \ No newline at end of file diff --git a/models/ISSOIntegrationProvider.cfc b/models/ISSOIntegrationProvider.cfc index a2b3878..2666ed1 100644 --- a/models/ISSOIntegrationProvider.cfc +++ b/models/ISSOIntegrationProvider.cfc @@ -1,6 +1,5 @@ interface { public string function getName(); - public string function getIconURL(); public string function startAuthenticationWorflow( required any event ); public any function processAuthorizationEvent( required any event ); } \ No newline at end of file diff --git a/models/ProviderService.cfc b/models/ProviderService.cfc index 9d23cee..5209dc2 100644 --- a/models/ProviderService.cfc +++ b/models/ProviderService.cfc @@ -34,7 +34,6 @@ component accessors="true" singleton threadsafe { var provider = variables.providers[ name ]; return { "name": provider.getName(), - "iconURL": provider.getIconURL(), "url": "/cbsso/auth/#provider.getName()#/start" }; }); diff --git a/models/providers/FacebookProvider.cfc b/models/providers/FacebookProvider.cfc index bcdbfd4..aad10e8 100644 --- a/models/providers/FacebookProvider.cfc +++ b/models/providers/FacebookProvider.cfc @@ -26,10 +26,6 @@ component return variables.name; } - public string function getIconURL(){ - return ""; - } - public string function getRedirectUri(){ if( !isNull( variables.redirectUri ) ){ return variables.redirectUri; diff --git a/models/providers/GitHubProvider.cfc b/models/providers/GitHubProvider.cfc index c7f6dda..24d3208 100644 --- a/models/providers/GitHubProvider.cfc +++ b/models/providers/GitHubProvider.cfc @@ -26,10 +26,6 @@ component return variables.name; } - public string function getIconURL(){ - return ""; - } - public string function getRedirectUri(){ if( !isNull( variables.redirectUri ) ){ return variables.redirectUri; @@ -37,7 +33,7 @@ component var protocol = cgi.HTTPS == "" ? "http://" : "https://"; - return "#protocol##cgi.HTTP_HOST#/oauth/auth/#variables.name.lcase()#"; + return "#protocol##cgi.HTTP_HOST#/cbsso/auth/#variables.name.lcase()#"; } public string function startAuthenticationWorflow( required any event ){ @@ -94,7 +90,7 @@ component .setWasSuccessful( true ) .setName( userData.name ) .setEmail( userData.email ) - .setUserId( userData.id ) + .setUserId( userData.id ); } catch( any e ){ return authResponse diff --git a/models/providers/GoogleProvider.cfc b/models/providers/GoogleProvider.cfc index 96e2be1..603469b 100644 --- a/models/providers/GoogleProvider.cfc +++ b/models/providers/GoogleProvider.cfc @@ -24,10 +24,6 @@ component return variables.name; } - public string function getIconURL(){ - return ""; - } - public string function getRedirectUri(){ if( !isNull( variables.redirectUri ) ){ return variables.redirectUri; diff --git a/models/providers/MicrosoftSAMLProvider.cfc b/models/providers/MicrosoftSAMLProvider.cfc index 5061e18..eda4799 100644 --- a/models/providers/MicrosoftSAMLProvider.cfc +++ b/models/providers/MicrosoftSAMLProvider.cfc @@ -16,10 +16,6 @@ component return variables.name; } - public string function getIconURL(){ - return ""; - } - public string function getRedirectUri(){ if( !isNull( variables.redirectUri ) ){ return variables.redirectUri; @@ -52,9 +48,8 @@ component try { var decoded = binaryDecode( event.getValue( "SAMLResponse" ), "base64" ); var data = charsetEncode( decoded, "utf-8" ); - writeDUmp( data ); - abort; var xmlData = xmlParse( data.reREplace( 'xmlns=".+?"', '', "all" ) ); + authResponse.setRawResponseData( data ); if( !detectSuccess( xmlData ) ){ diff --git a/test-harness/box.json b/test-harness/box.json index 20adb71..2e8eebc 100644 --- a/test-harness/box.json +++ b/test-harness/box.json @@ -5,16 +5,19 @@ "private":true, "description":"", "dependencies":{ - "coldbox":"^6.0.0" + "coldbox":"^6.0.0", + "cbsecurity":"^3.4.3+5" }, "devDependencies":{ + "cbjavaloader":"^2.0.0", "testbox":"*", - "cbPlaywright": "^1.33.2" + "cbPlaywright":"^1.33.2" }, "installPaths":{ "coldbox":"coldbox/", "testbox":"testbox/", - "cbPlaywright":"modules/cbPlaywright/" + "cbPlaywright":"modules/cbPlaywright/", + "cbsecurity":"modules/cbsecurity/" }, "testbox":{ "runner":"http://localhost:60299/tests/runner.cfm" diff --git a/test-harness/config/Coldbox.cfc b/test-harness/config/Coldbox.cfc index 5163672..db6004e 100644 --- a/test-harness/config/Coldbox.cfc +++ b/test-harness/config/Coldbox.cfc @@ -30,6 +30,35 @@ moduleSettings = { "cbsso" : { + "providers" : [ + { + type: "GitHubProvider@cbsso", + clientId : getJavaSystem().getProperty( "GITHUB_CLIENT_ID" ), + clientSecret : getJavaSystem().getProperty( "GITHUB_CLIENT_SECRET" ) + } + ] + } + }; + + moduleSettings = { + cbauth = { + // This is the path to your user object that contains the credential + // validation methods + userServiceClass = "models.UserService" + }, + "cbsecurity": { + authentication : { + // The WireBox ID of the authentication service to use which must adhere to the cbsecurity.interfaces.IAuthService interface. + "provider" : "authenticationService@cbauth", + // WireBox ID of the user service to use when leveraging user authentication, we default this to whatever is set + // by cbauth or basic authentication. (Optional) + "userService" : "cbauth.userServiceclass", + // The name of the variable to use to store an authenticated user in prc scope on all incoming authenticated requests + "prcUserVariable" : "oCurrentUser" + } + }, + "cbsso" : { + "enableCBAuthIntegration": true, "providers" : [ { type: "CustomProvider" diff --git a/test-harness/models/User.cfc b/test-harness/models/User.cfc new file mode 100644 index 0000000..98ad24c --- /dev/null +++ b/test-harness/models/User.cfc @@ -0,0 +1,19 @@ +component accessors = true { + property name = "id"; + property name = "Email"; + + function getId(){ + return variables.id; + } + + boolean function hasPermission( required permission ){ + return true; + } + + /** + * Shortcut to verify it the user is logged in or not. + */ + boolean function isLoggedIn(){ + return true; + } +} \ No newline at end of file diff --git a/test-harness/models/UserService.cfc b/test-harness/models/UserService.cfc new file mode 100644 index 0000000..d50a6c6 --- /dev/null +++ b/test-harness/models/UserService.cfc @@ -0,0 +1,83 @@ +component { + + /** + * This function is used to tell cbSSO which user is associated with an ssoAuthorizationResponse. + * + * @param ssoAuthorizationResponse An instance of ISSOAuthorizationResponse that was successful + * @param provider The configured provider used for this SSO event + * + * @return An cbAuth.models.IUser instance or null + */ + public any function findBySSO( required any ssoAuthorizationResponse, required any provider ); + + /** + * Create a new user based off of information from the ISSOAuthorizationResponse. + * + * @param ssoAuthorizationResponse An instance of ISSOAuthorizationResponse that was successful + * @param provider The configured provider used for this SSO event + * + * @return An cbAuth.models.IUser instance + */ + public any function createFromSSO( required any ssoAuthorizationResponse, required any provider ); + + /** + * Create a new user based off of information from the ISSOAuthorizationResponse. + * + * @param ssoAuthorizationResponse An instance of ISSOAuthorizationResponse that was successful + * @param provider The configured provider used for this SSO event + * + * @return An cbAuth.models.IUser instance + */ + public void function updateFromSSO( required any user, required any ssoAuthorizationResponse, required any provider ); + + public any function findBySSO( required any ssoAuthorizationResponse, required any provider ){ + return; + } + + public any function createFromSSO( required any ssoEvent, required any provider ){ + var a = new User(); + + a.setEmail( ssoEvent.getEmail() ) + .setId( ssoEvent.getUserId() ); + + return a; + } + + public any function updateFromSSO( required any user, required any ssoEvent, required any provider ){ + + } + + /** + * Verify if the incoming username/password are valid credentials. + * + * @username The username + * @password The password + */ + boolean function isValidCredentials( required username, required password ){ + return false; + } + + /** + * Retrieve a user by username + * + * @return User that implements IAuthUser + */ + function retrieveUserByUsername( required username ){ + var a = new User(); + a.setEmail( "fake" ); + return a; + } + + /** + * Retrieve a user by unique identifier + * + * @id The unique identifier + * + * @return User that implements IAuthUser + */ + function retrieveUserById( required id ){ + var a = new User(); + a.setEmail( "fake" ); + return a; + } +} \ No newline at end of file diff --git a/test-harness/views/main/index.cfm b/test-harness/views/main/index.cfm index 992cabe..0a4178f 100644 --- a/test-harness/views/main/index.cfm +++ b/test-harness/views/main/index.cfm @@ -1,3 +1,14 @@ Module Tester + + + try{ + + user = getInstance( "AuthenticationService@cbauth" ).getUser(); + writeDUmp( user ); + } + catch( NoUserLoggedIn e ){ + writeDump( [ "no user" ] ); + } + \ No newline at end of file