diff --git a/universal-application-tool-0.0.1/app/auth/AdminAuthClient.java b/universal-application-tool-0.0.1/app/auth/AdminAuthClient.java new file mode 100644 index 0000000000..723dedfcab --- /dev/null +++ b/universal-application-tool-0.0.1/app/auth/AdminAuthClient.java @@ -0,0 +1,13 @@ +package auth; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.inject.Qualifier; + +/** + * AdminAuthClient is the annotation for the auth client responsible for admin authentication. This + * client must implement {@link org.pac4j.core.client.IndirectClient}. + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface AdminAuthClient {} diff --git a/universal-application-tool-0.0.1/app/auth/ApplicantAuthClient.java b/universal-application-tool-0.0.1/app/auth/ApplicantAuthClient.java new file mode 100644 index 0000000000..2c49076c98 --- /dev/null +++ b/universal-application-tool-0.0.1/app/auth/ApplicantAuthClient.java @@ -0,0 +1,14 @@ +package auth; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.inject.Qualifier; + +/** + * ApplicantAuthClient is the annotation for the auth client responsible for applicant + * authentication. This client must implement IndirectClient -> {@link + * org.pac4j.core.client.IndirectClient}. + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ApplicantAuthClient {} diff --git a/universal-application-tool-0.0.1/app/auth/oidc/AdOidcClient.java b/universal-application-tool-0.0.1/app/auth/oidc/AdOidcClient.java deleted file mode 100644 index baf43a0655..0000000000 --- a/universal-application-tool-0.0.1/app/auth/oidc/AdOidcClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package auth.oidc; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import javax.inject.Qualifier; - -/** - * AdOidcClient is an annotation for AD-flavored OidcClient. - * - *

See {@link modules.SecurityModule#provideAdClient}. - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface AdOidcClient {} diff --git a/universal-application-tool-0.0.1/app/auth/oidc/AdOidcProvider.java b/universal-application-tool-0.0.1/app/auth/oidc/AdOidcProvider.java new file mode 100644 index 0000000000..fdaf1012fb --- /dev/null +++ b/universal-application-tool-0.0.1/app/auth/oidc/AdOidcProvider.java @@ -0,0 +1,91 @@ +package auth.oidc; + +import static com.google.common.base.Preconditions.checkNotNull; + +import auth.ProfileFactory; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.typesafe.config.Config; +import java.util.ArrayList; +import java.util.Collections; +import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver; +import org.pac4j.oidc.client.OidcClient; +import org.pac4j.oidc.config.OidcConfiguration; +import repository.UserRepository; + +/** Provider class for the AD OIDC Client. */ +public class AdOidcProvider implements Provider { + + private final Config configuration; + private final String baseUrl; + private final ProfileFactory profileFactory; + private final Provider applicantRepositoryProvider; + + @Inject + public AdOidcProvider( + Config configuration, + ProfileFactory profileFactory, + Provider applicantRepositoryProvider) { + this.configuration = checkNotNull(configuration); + this.baseUrl = configuration.getString("base_url"); + this.profileFactory = profileFactory; + this.applicantRepositoryProvider = applicantRepositoryProvider; + } + + @Override + public OidcClient get() { + if (!configuration.hasPath("adfs.client_id") || !configuration.hasPath("adfs.secret")) { + return null; + } + OidcConfiguration config = new OidcConfiguration(); + // Resource identifier that tells AD that this is civiform from the portal. + config.setClientId(configuration.getString("adfs.client_id")); + + // The token that we created within AD and use to sign our requests. + config.setSecret(configuration.getString("adfs.secret")); + + // Endpoint that app can use to get the public keys from. + config.setDiscoveryURI(configuration.getString("adfs.discovery_uri")); + + // Tells AD to use a post response when it sends info back from + // the auth request. + config.setResponseMode("form_post"); + + // Tells AD to give us an id token back from this request. + config.setResponseType("id_token"); + + // Scopes are the other things that we want from the AD endpoint + // (needs to also be configured on AD side). + // Note: ADFS has the extra claim: allatclaims which returns + // access token in the id_token. + String[] defaultScopes = {"openid", "profile", "email"}; + String[] extraScopes = configuration.getString("adfs.additional_scopes").split(" "); + ArrayList allClaims = new ArrayList<>(); + Collections.addAll(allClaims, defaultScopes); + Collections.addAll(allClaims, extraScopes); + config.setScope(String.join(" ", allClaims)); + + // Security setting that adds a random number to ensure cannot be reused. + config.setUseNonce(true); + + // Don't have custom state data. + config.setWithState(false); + + OidcClient client = new OidcClient(config); + client.setName("AdClient"); + + // Telling AD where to send people back to. This gets + // combined with the name to create the url. + client.setCallbackUrl(baseUrl + "/callback"); + + // This is specific to the implemention using pac4j. pac4j has concept + // of a profile for different identity profiles we have different creators. + // This is what links the user to the stuff they have access to. + client.setProfileCreator( + new AdfsProfileAdapter( + config, client, profileFactory, configuration, applicantRepositoryProvider)); + client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver()); + client.init(); + return client; + } +} diff --git a/universal-application-tool-0.0.1/app/auth/oidc/IdcsOidcClient.java b/universal-application-tool-0.0.1/app/auth/oidc/IdcsOidcClient.java deleted file mode 100644 index c9d5b87593..0000000000 --- a/universal-application-tool-0.0.1/app/auth/oidc/IdcsOidcClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package auth.oidc; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import javax.inject.Qualifier; - -/** - * IdcsOidcClient is an annotation for IDCS-flavored OidcClient. - * - *

See {@link modules.SecurityModule#provideIDCSClient}. - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface IdcsOidcClient {} diff --git a/universal-application-tool-0.0.1/app/auth/oidc/IdcsOidcProvider.java b/universal-application-tool-0.0.1/app/auth/oidc/IdcsOidcProvider.java new file mode 100644 index 0000000000..4f07dd8481 --- /dev/null +++ b/universal-application-tool-0.0.1/app/auth/oidc/IdcsOidcProvider.java @@ -0,0 +1,59 @@ +package auth.oidc; + +import static com.google.common.base.Preconditions.checkNotNull; + +import auth.ProfileFactory; +import com.google.inject.Inject; +import com.typesafe.config.Config; +import javax.inject.Provider; +import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver; +import org.pac4j.oidc.client.OidcClient; +import org.pac4j.oidc.config.OidcConfiguration; +import repository.UserRepository; + +public class IdcsOidcProvider implements Provider { + + private final Config configuration; + private final String baseUrl; + private final ProfileFactory profileFactory; + private final Provider applicantRepositoryProvider; + + @Inject + public IdcsOidcProvider( + Config configuration, + ProfileFactory profileFactory, + Provider applicantRepositoryProvider) { + this.configuration = checkNotNull(configuration); + this.baseUrl = configuration.getString("base_url"); + this.profileFactory = checkNotNull(profileFactory); + this.applicantRepositoryProvider = applicantRepositoryProvider; + } + + @Override + public OidcClient get() { + if (!configuration.hasPath("idcs.client_id") || !configuration.hasPath("idcs.secret")) { + return null; + } + OidcConfiguration config = new OidcConfiguration(); + config.setClientId(configuration.getString("idcs.client_id")); + config.setSecret(configuration.getString("idcs.secret")); + config.setDiscoveryURI(configuration.getString("idcs.discovery_uri")); + config.setResponseMode("form_post"); + // Our local fake IDCS doesn't support 'token' auth. + if (baseUrl.contains("localhost:")) { + config.setResponseType("id_token"); + } else { + config.setResponseType("id_token token"); + } + config.setUseNonce(true); + config.setWithState(false); + config.setScope("openid profile email"); + OidcClient client = new OidcClient(config); + client.setCallbackUrl(baseUrl + "/callback"); + client.setProfileCreator( + new IdcsProfileAdapter(config, client, profileFactory, applicantRepositoryProvider)); + client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver()); + client.init(); + return client; + } +} diff --git a/universal-application-tool-0.0.1/app/auth/saml/LoginRadiusSamlClient.java b/universal-application-tool-0.0.1/app/auth/saml/LoginRadiusSamlClient.java deleted file mode 100644 index c3af2fa247..0000000000 --- a/universal-application-tool-0.0.1/app/auth/saml/LoginRadiusSamlClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package auth.saml; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import javax.inject.Qualifier; - -/** - * LoginRadiusSamlClient is an annotation for SAML client customized for LoginRadius. - * - *

See {@link modules.SecurityModule#provideLoginRadiusClient}. - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface LoginRadiusSamlClient {} diff --git a/universal-application-tool-0.0.1/app/auth/saml/LoginRadiusSamlProvider.java b/universal-application-tool-0.0.1/app/auth/saml/LoginRadiusSamlProvider.java new file mode 100644 index 0000000000..f89f2e4d4d --- /dev/null +++ b/universal-application-tool-0.0.1/app/auth/saml/LoginRadiusSamlProvider.java @@ -0,0 +1,88 @@ +package auth.saml; + +import static com.google.common.base.Preconditions.checkNotNull; + +import auth.ProfileFactory; +import com.google.inject.Inject; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigException; +import java.util.IllegalFormatException; +import java.util.Optional; +import javax.inject.Provider; +import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver; +import org.pac4j.saml.client.SAML2Client; +import org.pac4j.saml.config.SAML2Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import repository.UserRepository; + +public class LoginRadiusSamlProvider implements Provider { + + private static final Logger logger = LoggerFactory.getLogger(LoginRadiusSamlProvider.class); + + private final Config configuration; + private final ProfileFactory profileFactory; + private final Provider applicantRepositoryProvider; + private final String baseUrl; + + @Inject + public LoginRadiusSamlProvider( + Config configuration, + ProfileFactory profileFactory, + Provider applicantRepositoryProvider) { + this.configuration = checkNotNull(configuration); + this.profileFactory = checkNotNull(profileFactory); + this.baseUrl = configuration.getString("base_url"); + this.applicantRepositoryProvider = checkNotNull(applicantRepositoryProvider); + } + + @Override + public SAML2Client get() { + if (!configuration.hasPath("login_radius.keystore_password") + || !configuration.hasPath("login_radius.private_key_password") + || !configuration.hasPath("login_radius.api_key")) { + return null; + } + + Optional metadataResourceUrlOpt = formatMetadataResourceUrl(); + + if (metadataResourceUrlOpt.isEmpty()) { + logger.warn("Invalid SAML metadata resource URL generated in LoginRadiusSamlProvider"); + return null; + } + + String metadataResourceUrl = metadataResourceUrlOpt.get(); + SAML2Configuration config = new SAML2Configuration(); + config.setKeystoreResourceFilepath(configuration.getString("login_radius.keystore_name")); + config.setKeystorePassword(configuration.getString("login_radius.keystore_password")); + config.setPrivateKeyPassword(configuration.getString("login_radius.private_key_password")); + config.setIdentityProviderMetadataResourceUrl(metadataResourceUrl); + SAML2Client client = new SAML2Client(config); + + client.setProfileCreator( + new SamlCiviFormProfileAdapter( + config, client, profileFactory, applicantRepositoryProvider)); + + client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver()); + client.setCallbackUrl(baseUrl + "/callback"); + client.init(); + return client; + } + + private Optional formatMetadataResourceUrl() { + try { + String metadataResourceUrl = + String.format( + "%s?apikey=%s&appName=%s", + configuration.getString("login_radius.metadata_uri"), + configuration.getString("login_radius.api_key"), + configuration.getString("login_radius.saml_app_name")); + return Optional.of(metadataResourceUrl); + } catch (IllegalFormatException + | NullPointerException + | ConfigException.Missing + | ConfigException.WrongType e) { + return Optional.empty(); + } + } +} diff --git a/universal-application-tool-0.0.1/app/controllers/LegacyRoutesController.java b/universal-application-tool-0.0.1/app/controllers/LegacyRoutesController.java new file mode 100644 index 0000000000..b39c7cc1e3 --- /dev/null +++ b/universal-application-tool-0.0.1/app/controllers/LegacyRoutesController.java @@ -0,0 +1,26 @@ +package controllers; + +import static play.mvc.Results.redirect; + +import java.util.Optional; +import play.mvc.Http; +import play.mvc.Result; + +/** + * Class for handling legacy routes after they have been removed between release cycles. All the + * routes in this class should eventually be deleted. + */ +public class LegacyRoutesController { + + public Result idcsLoginWithRedirect(Http.Request request, Optional redirectTo) { + return redirect(routes.LoginController.applicantLogin(redirectTo)); + } + + public Result adfsLogin(Http.Request request) { + return redirect(routes.LoginController.adminLogin()); + } + + public Result loginRadiusLoginWithRedirect(Http.Request request, Optional redirectTo) { + return redirect(routes.LoginController.applicantLogin(redirectTo)); + } +} diff --git a/universal-application-tool-0.0.1/app/controllers/LoginController.java b/universal-application-tool-0.0.1/app/controllers/LoginController.java index ffbd72843a..de86bc2ea4 100644 --- a/universal-application-tool-0.0.1/app/controllers/LoginController.java +++ b/universal-application-tool-0.0.1/app/controllers/LoginController.java @@ -1,12 +1,10 @@ package controllers; -import static com.google.common.base.Preconditions.checkNotNull; import static controllers.CallbackController.REDIRECT_TO_SESSION_KEY; +import auth.AdminAuthClient; +import auth.ApplicantAuthClient; import auth.AuthIdentityProviderName; -import auth.oidc.AdOidcClient; -import auth.oidc.IdcsOidcClient; -import auth.saml.LoginRadiusSamlClient; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.typesafe.config.Config; @@ -22,7 +20,6 @@ import org.pac4j.oidc.config.OidcConfiguration; import org.pac4j.play.PlayWebContext; import org.pac4j.play.http.PlayHttpActionAdapter; -import org.pac4j.saml.client.SAML2Client; import play.mvc.Controller; import play.mvc.Http; import play.mvc.Result; @@ -33,11 +30,9 @@ */ public class LoginController extends Controller { - private final OidcClient idcsClient; + private final IndirectClient adminClient; - private final OidcClient adClient; - - private final SAML2Client loginRadiusClient; + private final IndirectClient applicantClient; private final SessionStore sessionStore; @@ -45,56 +40,55 @@ public class LoginController extends Controller { private final Config config; - private final String applicantIdp; - @Inject public LoginController( - @AdOidcClient @Nullable OidcClient adClient, - @IdcsOidcClient @Nullable OidcClient idcsClient, - @LoginRadiusSamlClient @Nullable SAML2Client loginRadiusClient, + @AdminAuthClient @Nullable IndirectClient adminClient, + @ApplicantAuthClient @Nullable IndirectClient applicantClient, SessionStore sessionStore, Config config) { - this.idcsClient = idcsClient; - this.adClient = adClient; - this.loginRadiusClient = loginRadiusClient; + this.adminClient = adminClient; + this.applicantClient = applicantClient; this.sessionStore = Preconditions.checkNotNull(sessionStore); - this.applicantIdp = checkNotNull(config).getString("auth.applicant_idp"); this.httpActionAdapter = PlayHttpActionAdapter.INSTANCE; this.config = config; } - public Result loginWithRedirect(Http.Request request, Optional redirectTo) { - if (applicantIdp.equals(AuthIdentityProviderName.LOGIN_RADIUS_APPLICANT.getString())) { - return loginRadiusLoginWithRedirect(request, redirectTo); - } - return idcsLoginWithRedirect(request, redirectTo); - } - - public Result idcsLogin(Http.Request request) { - return login(request, idcsClient); + public Result adminLogin(Http.Request request) { + return login(request, adminClient); } - public Result idcsLoginWithRedirect(Http.Request request, Optional redirectTo) { + public Result applicantLogin(Http.Request request, Optional redirectTo) { if (redirectTo.isEmpty()) { - return idcsLogin(request); + return login(request, applicantClient); } - return login(request, idcsClient) + return login(request, applicantClient) .addingToSession(request, REDIRECT_TO_SESSION_KEY, redirectTo.get()); } - public Result loginRadiusLogin(Http.Request request) { - return login(request, loginRadiusClient); - } + public Result register(Http.Request request) { + String idp; + try { + idp = config.getString("auth.applicant_idp"); + } catch (ConfigException.Missing e) { + // Default to IDCS. + idp = AuthIdentityProviderName.IDCS_APPLICANT.toString(); + } - public Result loginRadiusLoginWithRedirect(Http.Request request, Optional redirectTo) { - if (redirectTo.isEmpty()) { - return loginRadiusLogin(request); + boolean isIDCS = idp.equals(AuthIdentityProviderName.IDCS_APPLICANT.toString()); + + // Because this is only being called when we know IDCS is available, this route should + // technically + // never happen. + if (!isIDCS) { + return login(request, applicantClient); } - return login(request, loginRadiusClient) - .addingToSession(request, REDIRECT_TO_SESSION_KEY, redirectTo.get()); + + return idcsRegister(request); } - public Result register(Http.Request request) { + // IDCS has specific register behavior that is different from other IDPs, which have the register + // option on the same screen as the login page. + private Result idcsRegister(Http.Request request) { String registerUrl = null; try { registerUrl = config.getString("idcs.register_uri"); @@ -110,11 +104,7 @@ public Result register(Http.Request request) { .addingToSession( request, REDIRECT_TO_SESSION_KEY, - routes.LoginController.idcsLoginWithRedirect(Optional.empty()).url()); - } - - public Result adfsLogin(Http.Request request) { - return login(request, adClient); + routes.LoginController.applicantLogin(Optional.empty()).url()); } // Logic taken from org.pac4j.play.deadbolt2.Pac4jHandler.beforeAuthCheck. diff --git a/universal-application-tool-0.0.1/app/modules/SecurityModule.java b/universal-application-tool-0.0.1/app/modules/SecurityModule.java index 69b236f440..2c8816c5f3 100644 --- a/universal-application-tool-0.0.1/app/modules/SecurityModule.java +++ b/universal-application-tool-0.0.1/app/modules/SecurityModule.java @@ -4,50 +4,44 @@ import static play.mvc.Results.forbidden; import static play.mvc.Results.redirect; +import auth.AdminAuthClient; +import auth.ApplicantAuthClient; +import auth.AuthIdentityProviderName; import auth.Authorizers; import auth.CiviFormProfileData; import auth.FakeAdminClient; import auth.GuestClient; import auth.ProfileFactory; import auth.Roles; -import auth.oidc.AdOidcClient; -import auth.oidc.AdfsProfileAdapter; -import auth.oidc.IdcsOidcClient; -import auth.oidc.IdcsProfileAdapter; -import auth.saml.LoginRadiusSamlClient; -import auth.saml.SamlCiviFormProfileAdapter; +import auth.oidc.AdOidcProvider; +import auth.oidc.IdcsOidcProvider; +import auth.saml.LoginRadiusSamlProvider; import com.google.common.collect.ImmutableMap; import com.google.inject.AbstractModule; +import com.google.inject.ConfigurationException; import com.google.inject.Provides; import com.google.inject.Singleton; import controllers.routes; import java.net.URI; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Random; import javax.annotation.Nullable; -import javax.inject.Provider; import org.pac4j.core.authorization.authorizer.RequireAllRolesAuthorizer; import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer; import org.pac4j.core.client.Client; import org.pac4j.core.client.Clients; +import org.pac4j.core.client.IndirectClient; import org.pac4j.core.config.Config; import org.pac4j.core.context.HttpConstants; import org.pac4j.core.context.session.SessionStore; -import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver; -import org.pac4j.oidc.client.OidcClient; -import org.pac4j.oidc.config.OidcConfiguration; import org.pac4j.play.CallbackController; import org.pac4j.play.LogoutController; import org.pac4j.play.http.PlayHttpActionAdapter; import org.pac4j.play.store.PlayCookieSessionStore; import org.pac4j.play.store.ShiroAesDataEncrypter; -import org.pac4j.saml.client.SAML2Client; -import org.pac4j.saml.config.SAML2Configuration; import play.Environment; -import repository.UserRepository; /** SecurityModule configures and initializes all authentication and authorization classes. */ public class SecurityModule extends AbstractModule { @@ -101,175 +95,71 @@ protected void configure() { PlayCookieSessionStore sessionStore = new PlayCookieSessionStore(new ShiroAesDataEncrypter(aesKey)); bind(SessionStore.class).toInstance(sessionStore); - } - @Provides - @Singleton - protected GuestClient guestClient(ProfileFactory profileFactory) { - return new GuestClient(profileFactory); + String applicantAuthClient = "idcs"; + + try { + applicantAuthClient = configuration.getString("auth.applicant_idp"); + } catch (ConfigurationException ignore) { + // Default to IDCS. + } + + bindAdminIdpProvider(); + bindApplicantIdpProvider(applicantAuthClient); } - @Provides - @Singleton - protected FakeAdminClient fakeAdminClient(ProfileFactory profileFactory) { - return new FakeAdminClient(profileFactory, this.configuration); + private void bindAdminIdpProvider() { + // Currently the only supported admin auth provider. As we add other admin auth providers, + // this can be converted into a switch statement. + bind(IndirectClient.class) + .annotatedWith(AdminAuthClient.class) + .toProvider(AdOidcProvider.class); } - /** Creates a singleton object of OidcClient configured for IDCS and initializes it on startup. */ - @Provides - @Nullable - @Singleton - @IdcsOidcClient - protected OidcClient provideIDCSClient( - ProfileFactory profileFactory, Provider applicantRepositoryProvider) { - if (!this.configuration.hasPath("idcs.client_id") - || !this.configuration.hasPath("idcs.secret")) { - return null; - } - OidcConfiguration config = new OidcConfiguration(); - config.setClientId(this.configuration.getString("idcs.client_id")); - config.setSecret(this.configuration.getString("idcs.secret")); - config.setDiscoveryURI(this.configuration.getString("idcs.discovery_uri")); - config.setResponseMode("form_post"); - // Our local fake IDCS doesn't support 'token' auth. - if (baseUrl.contains("localhost:")) { - config.setResponseType("id_token"); - } else { - config.setResponseType("id_token token"); + private void bindApplicantIdpProvider(String applicantIdpName) { + AuthIdentityProviderName idpName = AuthIdentityProviderName.forString(applicantIdpName).get(); + + switch (idpName) { + case LOGIN_RADIUS_APPLICANT: + bind(IndirectClient.class) + .annotatedWith(ApplicantAuthClient.class) + .toProvider(LoginRadiusSamlProvider.class); + break; + case IDCS_APPLICANT: + default: + bind(IndirectClient.class) + .annotatedWith(ApplicantAuthClient.class) + .toProvider(IdcsOidcProvider.class); } - config.setUseNonce(true); - config.setWithState(false); - config.setScope("openid profile email"); - OidcClient client = new OidcClient(config); - client.setCallbackUrl(baseUrl + "/callback"); - client.setProfileCreator( - new IdcsProfileAdapter(config, client, profileFactory, applicantRepositoryProvider)); - client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver()); - client.init(); - return client; } - /** - * Creates a singleton object of SAML2Client configured for LoginRadius and initializes it on - * startup. - */ @Provides - @Nullable @Singleton - @LoginRadiusSamlClient - protected SAML2Client provideLoginRadiusClient( - ProfileFactory profileFactory, Provider applicantRepositoryProvider) { - if (!this.configuration.hasPath("login_radius.keystore_password") - || !this.configuration.hasPath("login_radius.private_key_password") - || !this.configuration.hasPath("login_radius.api_key")) { - return null; - } - - String metadataResourceUrl = - String.format( - "%s?apikey=%s&appName=%s", - this.configuration.getString("login_radius.metadata_uri"), - this.configuration.getString("login_radius.api_key"), - this.configuration.getString("login_radius.saml_app_name")); - SAML2Configuration config = new SAML2Configuration(); - config.setKeystoreResourceFilepath(this.configuration.getString("login_radius.keystore_name")); - config.setKeystorePassword(this.configuration.getString("login_radius.keystore_password")); - config.setPrivateKeyPassword(this.configuration.getString("login_radius.private_key_password")); - config.setIdentityProviderMetadataResourceUrl(metadataResourceUrl); - SAML2Client client = new SAML2Client(config); - - client.setProfileCreator( - new SamlCiviFormProfileAdapter( - config, client, profileFactory, applicantRepositoryProvider)); - - client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver()); - client.setCallbackUrl(baseUrl + "/callback"); - client.init(); - return client; + protected GuestClient guestClient(ProfileFactory profileFactory) { + return new GuestClient(profileFactory); } - /** Creates a singleton object of OidcClient configured for AD and initializes it on startup. */ @Provides - @Nullable @Singleton - @AdOidcClient - protected OidcClient provideAdClient( - ProfileFactory profileFactory, Provider applicantRepositoryProvider) { - if (!this.configuration.hasPath("adfs.client_id") - || !this.configuration.hasPath("adfs.secret")) { - return null; - } - OidcConfiguration config = new OidcConfiguration(); - // Resource identifier that tells AD that this is civiform from the portal. - config.setClientId(this.configuration.getString("adfs.client_id")); - - // The token that we created within AD and use to sign our requests. - config.setSecret(this.configuration.getString("adfs.secret")); - - // Endpoint that app can use to get the public keys from. - config.setDiscoveryURI(this.configuration.getString("adfs.discovery_uri")); - - // Tells AD to use a post response when it sends info back from - // the auth request. - config.setResponseMode("form_post"); - - // Tells AD to give us an id token back from this request. - config.setResponseType("id_token"); - - // Scopes are the other things that we want from the AD endpoint - // (needs to also be configured on AD side). - // Note: ADFS has the extra claim: allatclaims which returns - // access token in the id_token. - String[] defaultScopes = {"openid", "profile", "email"}; - String[] extraScopes = this.configuration.getString("adfs.additional_scopes").split(" "); - ArrayList allClaims = new ArrayList<>(); - Collections.addAll(allClaims, defaultScopes); - Collections.addAll(allClaims, extraScopes); - config.setScope(String.join(" ", allClaims)); - - // Security setting that adds a random number to ensure cannot be reused. - config.setUseNonce(true); - - // Don't have custom state data. - config.setWithState(false); - - OidcClient client = new OidcClient(config); - client.setName("AdClient"); - - // Telling AD where to send people back to. This gets - // combined with the name to create the url. - client.setCallbackUrl(baseUrl + "/callback"); - - // This is specific to the implemention using pac4j. pac4j has concept - // of a profile for different identity profiles we have different creators. - // This is what links the user to the stuff they have access to. - client.setProfileCreator( - new AdfsProfileAdapter( - config, client, profileFactory, this.configuration, applicantRepositoryProvider)); - client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver()); - client.init(); - return client; + protected FakeAdminClient fakeAdminClient(ProfileFactory profileFactory) { + return new FakeAdminClient(profileFactory, this.configuration); } @Provides @Singleton protected Config provideConfig( GuestClient guestClient, - @AdOidcClient @Nullable OidcClient adClient, - @IdcsOidcClient @Nullable OidcClient idcsClient, - @LoginRadiusSamlClient @Nullable SAML2Client loginRadiusClient, + @ApplicantAuthClient @Nullable IndirectClient applicantAuthClient, + @AdminAuthClient @Nullable IndirectClient adminAuthClient, FakeAdminClient fakeAdminClient) { List clientList = new ArrayList<>(); - clientList.add(guestClient); - if (idcsClient != null) { - clientList.add(idcsClient); + if (applicantAuthClient != null) { + clientList.add(applicantAuthClient); } - if (loginRadiusClient != null) { - clientList.add(loginRadiusClient); - } - if (adClient != null) { - clientList.add(adClient); + if (adminAuthClient != null) { + clientList.add(adminAuthClient); } + clientList.add(guestClient); if (fakeAdminClient.canEnable(URI.create(baseUrl).getHost())) { clientList.add(fakeAdminClient); } diff --git a/universal-application-tool-0.0.1/app/views/LoginForm.java b/universal-application-tool-0.0.1/app/views/LoginForm.java index f0624266bd..4b8c438710 100644 --- a/universal-application-tool-0.0.1/app/views/LoginForm.java +++ b/universal-application-tool-0.0.1/app/views/LoginForm.java @@ -177,7 +177,7 @@ private ContainerTag debugContent() { private Tag loginButton(Messages messages) { String msg = messages.at(MessageKey.BUTTON_LOGIN.getKeyName()); return redirectButton( - applicantIdp, msg, routes.LoginController.loginWithRedirect(Optional.empty()).url()) + applicantIdp, msg, routes.LoginController.applicantLogin(Optional.empty()).url()) .withClasses(BaseStyles.LOGIN_REDIRECT_BUTTON); } @@ -197,7 +197,7 @@ private Tag guestButton(Messages messages) { private Tag adminLink(Messages messages) { String msg = messages.at(MessageKey.LINK_ADMIN_LOGIN.getKeyName()); return a(msg) - .withHref(routes.LoginController.adfsLogin().url()) + .withHref(routes.LoginController.adminLogin().url()) .withClasses(BaseStyles.ADMIN_LOGIN); } } diff --git a/universal-application-tool-0.0.1/app/views/applicant/ApplicantUpsellCreateAccountView.java b/universal-application-tool-0.0.1/app/views/applicant/ApplicantUpsellCreateAccountView.java index 895489e0dd..e206cf3b28 100644 --- a/universal-application-tool-0.0.1/app/views/applicant/ApplicantUpsellCreateAccountView.java +++ b/universal-application-tool-0.0.1/app/views/applicant/ApplicantUpsellCreateAccountView.java @@ -89,8 +89,7 @@ public Content render( .with( new LinkElement() .setHref( - routes.LoginController.idcsLoginWithRedirect( - Optional.of(redirectTo)) + routes.LoginController.applicantLogin(Optional.of(redirectTo)) .url()) .setText( messages.at(MessageKey.LINK_CREATE_ACCOUNT_OR_SIGN_IN.getKeyName())) diff --git a/universal-application-tool-0.0.1/conf/routes b/universal-application-tool-0.0.1/conf/routes index 278cc3481a..eeaeff9372 100644 --- a/universal-application-tool-0.0.1/conf/routes +++ b/universal-application-tool-0.0.1/conf/routes @@ -139,11 +139,15 @@ POST /callback/:client_name controllers.CallbackController.callback(req # Log into application GET /loginForm controllers.HomeController.loginForm(request: Request, message: java.util.Optional[String]) -GET /idcsLogin controllers.LoginController.idcsLoginWithRedirect(request: Request, redirectTo: java.util.Optional[String]) +GET /adminLogin controllers.LoginController.adminLogin(request: Request) +GET /applicantLogin controllers.LoginController.applicantLogin(request: Request, redirectTo: java.util.Optional[String]) GET /idcsRegister controllers.LoginController.register(request: Request) -GET /adfsLogin controllers.LoginController.adfsLogin(request: Request) -GET /loginRadiusLogin controllers.LoginController.loginRadiusLoginWithRedirect(request: Request, redirectTo: java.util.Optional[String]) -GET /applicantlogin controllers.LoginController.loginWithRedirect(request: Request, redirectTo: java.util.Optional[String]) + +# Legacy login routes -- to be deleted +# Log into application +GET /idcsLogin controllers.LegacyRoutesController.idcsLoginWithRedirect(request: Request, redirectTo: java.util.Optional[String]) +GET /adfsLogin controllers.LegacyRoutesController.adfsLogin(request: Request) +GET /loginRadiusLogin controllers.LegacyRoutesController.loginRadiusLoginWithRedirect(request: Request, redirectTo: java.util.Optional[String]) # Log out of application GET /logout @org.pac4j.play.LogoutController.logout(request: Request) diff --git a/universal-application-tool-0.0.1/test/app/SecurityBrowserTest.java b/universal-application-tool-0.0.1/test/app/SecurityBrowserTest.java index b472657239..2a66f21421 100644 --- a/universal-application-tool-0.0.1/test/app/SecurityBrowserTest.java +++ b/universal-application-tool-0.0.1/test/app/SecurityBrowserTest.java @@ -33,7 +33,7 @@ public void getApplicantRepository() { } protected void loginWithSimulatedIdcs() { - goTo(routes.LoginController.idcsLoginWithRedirect(Optional.empty())); + goTo(routes.LoginController.applicantLogin(Optional.empty())); // If we are not cookied, enter a username and password. if (browser.pageSource().contains("Enter any login")) { browser.$("[name='login']").click();