25 | #if>
26 | @layout.registrationLayout>
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/rest/FormUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.rest;
15 |
16 | import javax.ws.rs.core.Response;
17 |
18 | import org.keycloak.authentication.AuthenticationFlowContext;
19 | import org.keycloak.models.utils.FormMessage;
20 |
21 | public class FormUtilities {
22 | public static final String ERROR_PAGE_TEMPLATE = "error-page.ftl";
23 |
24 | public static Response createErrorPage(AuthenticationFlowContext context, FormMessage msg) {
25 | return context.form()
26 | .addError(msg)
27 | .createForm(ERROR_PAGE_TEMPLATE);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/verify-keycloak/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | com.ibm.security.access.keycloak
5 | verify-keycloak-integration
6 | 1.0.0
7 | ..
8 |
9 | verify-keycloak
10 | pom
11 |
12 |
13 |
14 | com.ibm.security.access.authenticator
15 | IBMSecurityVerifyAuthenticators
16 | 1.0.0
17 | runtime
18 |
19 |
20 |
21 |
22 |
23 |
24 | maven-dependency-plugin
25 |
26 |
27 | package
28 |
29 | copy-dependencies
30 |
31 |
32 | ${project.build.directory}/jars
33 | runtime
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/utils/IBMSecurityVerifyLoggingUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.utils;
15 |
16 | import org.jboss.logging.Logger;
17 |
18 | public class IBMSecurityVerifyLoggingUtilities {
19 |
20 | public static void print(Logger logger, String msg) {
21 | logger.info(msg);
22 | }
23 |
24 | public static void error(Logger logger, String msg) {
25 | logger.error(msg);
26 | }
27 |
28 | public static void error(Logger logger, String methodName, String errorMsg) {
29 | logger.errorf("ERROR [%s] %s", methodName, errorMsg);
30 | }
31 |
32 | public static void entry(Logger logger, String methodName, Object... arguments) {
33 | if (logger.isTraceEnabled()) {
34 | logger.tracef("%s entry", methodName, arguments);
35 | }
36 | }
37 |
38 | public static void exit(Logger logger, String methodName) {
39 | exit(logger, methodName, null);
40 | }
41 |
42 | public static void exit(Logger logger, String methodName, Object returnValue) {
43 | if (logger.isTraceEnabled()) {
44 | if (returnValue != null) {
45 | logger.tracef("%s exit [%s]", methodName, returnValue);
46 | } else {
47 | logger.tracef("%s exit", methodName);
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/verify-registration.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
28 |
29 |
44 | #if>
45 | @layout.registrationLayout>
46 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/push-notification-login.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
28 |
29 |
44 | #if>
45 | @layout.registrationLayout>
46 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.ibm.security.access.keycloak
6 | verify-keycloak-integration
7 | 1.0.0
8 | ../
9 |
10 | com.ibm.security.access.authenticator
11 | IBMSecurityVerifyAuthenticators
12 | IBMSecurityVerifyAuthenticators
13 | jar
14 |
15 |
16 | 10.0.1
17 |
18 |
19 |
20 |
21 | org.keycloak
22 | keycloak-core
23 | ${keycloak.version}
24 | provided
25 |
26 |
27 | org.keycloak
28 | keycloak-server-spi-private
29 | ${keycloak.version}
30 | provided
31 |
32 |
33 | org.apache.httpcomponents
34 | httpclient
35 | 4.5.9
36 | provided
37 |
38 |
39 | org.keycloak
40 | keycloak-server-spi
41 | ${keycloak.version}
42 | provided
43 |
44 |
45 | org.jboss.spec.javax.ws.rs
46 | jboss-jaxrs-api_2.1_spec
47 | 1.0.2.Final
48 | provided
49 |
50 |
51 | org.jboss.logging
52 | jboss-logging
53 | 3.4.0.Final
54 | provided
55 |
56 |
57 | org.jboss.resteasy
58 | resteasy-jaxrs
59 | 2.3.2.Final
60 | provided
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/push-notification-login-resend.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
28 |
31 |
32 |
45 | #if>
46 | @layout.registrationLayout>
47 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/otp-validation.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
45 |
46 | #if>
47 | @layout.registrationLayout>
48 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.ibm.security.access.keycloak
5 | verify-keycloak-integration
6 | 1.0.0
7 | pom
8 |
9 |
10 | IBMSecurityVerifyAuthenticators
11 | verify-keycloak
12 |
13 |
14 |
15 |
16 | docker-images
17 |
18 |
19 | Dockerfile
20 |
21 |
22 |
23 |
24 |
25 | org.codehaus.mojo
26 | exec-maven-plugin
27 | 1.6.0
28 |
29 |
30 | build-dockerfile
31 | install
32 |
33 | exec
34 |
35 |
36 | docker
37 | ${project.basedir}
38 |
39 | build
40 | -t
41 | ${project.name}:${project.version}
42 | .
43 |
44 |
45 |
46 |
47 | tag-image
48 | install
49 |
50 | exec
51 |
52 |
53 | docker
54 | ${project.build.directory}
55 |
56 | tag
57 | ${project.name}:${project.version}
58 | ${project.name}:latest
59 |
60 |
61 |
62 |
63 | push-image
64 | deploy
65 |
66 | exec
67 |
68 |
69 | docker
70 | ${project.build.directory}
71 |
72 | push
73 | ${project.name}
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/qr-login.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
29 |
32 |
33 |
62 | #if>
63 | @layout.registrationLayout>
64 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/otp-selection.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
53 | #if>
54 | @layout.registrationLayout>
55 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/verify/registration/IBMSecurityVerifyRegistrationRequiredActionAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.verify.registration;
15 |
16 | import org.jboss.logging.Logger;
17 | import org.keycloak.authentication.Authenticator;
18 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
19 | import org.keycloak.models.KeycloakSession;
20 |
21 | import com.ibm.security.verify.authenticator.AbstractIBMSecurityVerifyAuthenticatorFactory;
22 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
23 |
24 | public class IBMSecurityVerifyRegistrationRequiredActionAuthenticatorFactory extends AbstractIBMSecurityVerifyAuthenticatorFactory {
25 |
26 | public static final String ID = "verify-reg";
27 | private static final IBMSecurityVerifyRegistrationRequiredActionAuthenticator SINGLETON = new IBMSecurityVerifyRegistrationRequiredActionAuthenticator();
28 |
29 | private static final Requirement[] REQUIREMENT_CHOICES = {
30 | Requirement.REQUIRED
31 | };
32 |
33 | private Logger logger = Logger.getLogger(IBMSecurityVerifyRegistrationRequiredActionAuthenticatorFactory.class);
34 |
35 | public Authenticator create(KeycloakSession session) {
36 | final String methodName = "create";
37 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
38 | return SINGLETON;
39 | }
40 |
41 | public String getDisplayType() {
42 | final String methodName = "getDisplayType";
43 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
44 |
45 | String displayType = "IBM Security Verify Registration";
46 |
47 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, displayType);
48 | return displayType;
49 | }
50 |
51 | public String getHelpText() {
52 | final String methodName = "getHelpText";
53 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
54 |
55 | String helpText = "Register with IBM Verify. Requires an authenticated user in the current authentication context.";
56 |
57 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, helpText);
58 | return helpText;
59 | }
60 |
61 | public String getId() {
62 | final String methodName = "getId";
63 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
64 |
65 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, ID);
66 | return ID;
67 | }
68 |
69 | public Requirement[] getRequirementChoices() {
70 | final String methodName = "getRequirementChoices";
71 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
72 |
73 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, REQUIREMENT_CHOICES);
74 | return REQUIREMENT_CHOICES;
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/webauthn/registration/IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.webauthn.registration;
15 |
16 | import org.jboss.logging.Logger;
17 | import org.keycloak.authentication.Authenticator;
18 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
19 | import org.keycloak.models.KeycloakSession;
20 |
21 | import com.ibm.security.verify.authenticator.AbstractIBMSecurityVerifyAuthenticatorFactory;
22 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
23 |
24 | public class IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticatorFactory extends AbstractIBMSecurityVerifyAuthenticatorFactory {
25 |
26 | private static final String ID = "fido-reg";
27 | private static final IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator SINGLETON = new IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator();
28 |
29 | private static final Requirement[] REQUIREMENT_CHOICES = {
30 | Requirement.REQUIRED
31 | };
32 |
33 | private Logger logger = Logger.getLogger(IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticatorFactory.class);
34 |
35 | public Authenticator create(KeycloakSession session) {
36 | final String methodName = "create";
37 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
38 | return SINGLETON;
39 | }
40 |
41 | public String getDisplayType() {
42 | final String methodName = "getDisplayType";
43 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
44 |
45 | String displayName = "IBM Security Verify FIDO2 Registration";
46 |
47 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, displayName);
48 | return displayName;
49 | }
50 |
51 | public String getHelpText() {
52 | final String methodName = "getHelpText";
53 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
54 |
55 | String helpText = "Register your FIDO2 device. Requires an authenticated user in the current authentication context.";
56 |
57 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, helpText);
58 | return helpText;
59 | }
60 |
61 | public String getId() {
62 | final String methodName = "getId";
63 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
64 |
65 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, ID);
66 | return ID;
67 | }
68 |
69 | public Requirement[] getRequirementChoices() {
70 | final String methodName = "getRequirementChoices";
71 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
72 |
73 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, REQUIREMENT_CHOICES);
74 | return REQUIREMENT_CHOICES;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/messages/messages_en.properties:
--------------------------------------------------------------------------------
1 | ciConfigFqdn=Tenant Fully Qualified Domain Name
2 | ciConfigFqdnHelpText=The FQDN of your IBM Security Verify tenant
3 | ciConfigApiClientId=API Client ID
4 | ciConfigApiClientIdHelpText=Client ID from your IBM Security Verify API Client
5 | ciConfigApiClientSecret=API Client Secret
6 | ciConfigApiClientSecretHelpText=Client Secret from your IBM Security Verify API Client
7 | errorTitle=We are sorry...
8 | errorMsg=Please contact your system administrator.
9 | errorMsg2FARequired=User authentication context required.
10 | errorMsgAccessDenied=Authentication failed.
11 | errorMsgDeviceRegistrationFailed=Failed to register device with IBM Security Verify.
12 | errorMsgLoginCanceled=Authentication was canceled.
13 | errorMsgMissingEmail=User does not have an email address configured.
14 | errorMsgMissingEmailAndPhoneNumber=User does not have an email address or a phone number configured.
15 | errorMsgUserDoesNotExist=User does not exist.
16 | errorMsgUserRegistrationFailed=Failed to create IBM Security Verify account for user.
17 | fidoAuthenticator=IBM Security Verify FIDO2 Login Authenticator
18 | fidoAuthenticatorHelpText=Login using your FIDO2 device
19 | fidoFormLoginButton=Login with your FIDO2 device
20 | fidoFormRegisterButton=Not registered? Register your FIDO2 device
21 | fidoRegistrationAuthenticator=IBM Security Verify FIDO2 Registration
22 | fidoRegistrationAuthenticatorHelpText=Register your FIDO2 device. Requires an authenticated user in the current authentication context.
23 | fidoRegisterButton=Register your FIDO2 device
24 | fidoRegistrationVerified=FIDO2 device registration verified. Please sign in to continue.
25 | otpAuthenticator=IBM Security Verify OTP Authenticator
26 | otpAuthenticatorHelpText=Send a one time password via Email or SMS
27 | otpSelectionFormMsg=Select how you would like to receive a one time password (OTP)
28 | otpSelectionFormEmail=Email
29 | otpSelectionFormSms=SMS
30 | otpEmailSubmissionLabel=Enter your email OTP
31 | otpSmsSubmissionLabel=Enter your SMS OTP
32 | pushNotificationAuthenticator=IBM Security Verify Push Notification Login Authenticator
33 | pushNotificationAuthenticatorHelpText=Send a push notification to your IBM Verify Mobile App
34 | pushNotificationFormLoginSentMessage=A push notification has been sent to your device. Please complete your authentication on the IBM Verify Mobile App to proceed.
35 | pushNotificationFormExpiredError=Push notification expired.
36 | pushNotificationFormResendMessage=Press the button below to resend a push notification to your device.
37 | pushNotificationFormResendButton=Resend Notification
38 | qrAuthenticator=IBM Security Verify QR Login Authenticator
39 | qrAuthenticatorHelpText=Login by scanning a QR code with your IBM Verify Mobile App
40 | qrFormMessage=Scan the QR code below with your IBM Verify mobile application. Once scanned, your authentication will complete automatically.
41 | qrFormLoginTimeOutError=QR login timed out. Please try again.
42 | qrVerifyRegistrationRequiredError=Please register with IBM Verify to enable passwordless authentication.
43 | verifyAuthenticator=IBM Security Verify Registration
44 | verifyAuthenticatorHelpText=Register with IBM Verify. Requires an authenticated user in the current authentication context.
45 | verifyFormMessage=Scan this QR Code with your IBM Verify mobile application. Once scanned, your IBM Security Verify registration will be completed.
46 | verifyFormRegisterButton=Not registered? Register with IBM Verify
47 | verifyRegistrationRequired=IBM Security Verify registration required.
48 | verifyRegistrationVerified=IBM Security Verify registration verified. Please sign in to continue.
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IBM Security Verify Authenticator Extensions for Keycloak/RedHat SSO
2 |
3 | This repository contains development of a set of authenticator extensions for enhancing authentication capabilities with Keycloak and/or RedHat SSO with IBM Security Verify.
4 |
5 | ## Current Support Level
6 | Current version extensions support Keycloak 10.0.1 and RedHat SSO 7.4. Please refer to the Release Notes in the User Guide for version specific support levels.
7 |
8 | ## Usage
9 | Please refer to the User Guide attached to each [release](https://github.com/IBM-Security/verify-keycloak-integration/releases) for detailed information on deploying and using the extensions.
10 |
11 | ## Setting Up a Development Environment
12 | ### Software Requirements
13 | * IBM JRE 1.8
14 | * Docker
15 | * Maven
16 | * [IBM Security Verify (Free Version)](https://www.ibm.com/account/reg/signup?formid=urx-44536)
17 | * IBM Verify Mobile Application (iOS & Android)
18 |
19 | ### Build
20 | `mvn clean install` from the root directory - creates a verify-keycloak Docker image
21 |
22 | The build process creates a Keycloak/RedHat SSO compatible extensions JAR with the IBM Security Verify authenticator extensions. The JAR file is placed into a Keycloak SSO docker image into the `/standalone/deployments` directory and will be deployed automatically when the image is started. Please refer to [Deployment Scripts](#deployment-scripts) for a list of commands that manage the built image.
23 |
24 | ### Deployment Scripts
25 | In the /dev/scripts folder:
26 | * `start.sh` : starts the Keycloak instance with built extensions
27 | * `stop.sh` : stops the Keycloak server
28 | * `removeImage.sh` : remove existing Keycloak extensions image
29 | * `removeVolumes.sh` : remove all Keycloak instance data
30 | * `copyJars.sh` : copies newly built extensions JAR into running Keycloak instance
31 |
32 | ### Accessing the Keycloak Instance
33 | By default, the deployed instance can be accessed on [http://localhost:8080](http://localhost:8080)
34 |
35 | ## Contributing to the Verify Authenticator Extensions Project
36 | ### Process
37 | * Create a fork of `master` and name it after the feature you are implementing (e.g. account_attribute_support)
38 | * Follow the [Project Structure & Coding Guidelines](#project-structure--coding-guidelines)
39 | * Create a pull request when feature is complete
40 |
41 | ### Project Structure & Coding Guidelines
42 | * IBMSecurityVerifyAuthenticators
43 | * src/main/java
44 | * com.ibm.security.verify.authenticator - Abstract classes
45 | * com.ibm.security.verify.authenticator. - Authenticator specific implementations
46 | * com.ibm.security.verify.authenticator.rest - Authenticator ReST implementations
47 | * com.ibm.security.verify.authenticator.utils - Logging
48 | * src/main/resources
49 | * META-INF/services - Authenticator registration
50 | * theme-resources - Localization, images, and templates
51 | * verify-keycloak - Docker image builder
52 | * dev - Docker YAML file and utility scripts
53 | * pom.xml - Main Maven build file
54 |
55 | ## Demo Application
56 | [IBM Airways](https://github.com/jason-choi1/ibm-airways-keycloak-sso) is a demo application developed with ReactJS and Carbon Design System components that demonstrates Keycloak SSO integration in a consumer application. More information on usage can be found in the project's repository.
57 |
58 | ## Reporting Issues
59 | Please open a GitHub issue to report any problems with the authenticator. The repository is actively monitored
60 |
61 | ## Point of Contact
62 | Jason Choi:
63 |
64 | ## License
65 | [](https://opensource.org/licenses/Apache-2.0)
66 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/verify/qr/IBMSecurityVerifyQrLoginAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.verify.qr;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.Config.Scope;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.authentication.AuthenticatorFactory;
23 | import org.keycloak.models.AuthenticationExecutionModel;
24 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
25 | import org.keycloak.models.KeycloakSession;
26 | import org.keycloak.models.KeycloakSessionFactory;
27 | import org.keycloak.provider.ProviderConfigProperty;
28 |
29 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 |
32 | public class IBMSecurityVerifyQrLoginAuthenticatorFactory implements AuthenticatorFactory {
33 |
34 | public static final String ID = "qr-login-authenticator";
35 | private static final IBMSecurityVerifyQrLoginAuthenticator SINGLETON = new IBMSecurityVerifyQrLoginAuthenticator();
36 |
37 | private static final List CONFIG_PROPERTIES = new ArrayList();
38 |
39 | private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
40 | AuthenticationExecutionModel.Requirement.REQUIRED,
41 | AuthenticationExecutionModel.Requirement.ALTERNATIVE
42 | };
43 |
44 | static {
45 | ProviderConfigProperty property;
46 |
47 | property = new ProviderConfigProperty();
48 | property.setName(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
49 | property.setLabel("Tenant Fully Qualified Domain Name");
50 | property.setType(ProviderConfigProperty.STRING_TYPE);
51 | property.setHelpText("The FQDN of your IBM Security Verify tenant");
52 | CONFIG_PROPERTIES.add(property);
53 |
54 | property = new ProviderConfigProperty();
55 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
56 | property.setLabel("API Client ID");
57 | property.setType(ProviderConfigProperty.STRING_TYPE);
58 | property.setHelpText("Client ID from your IBM Security Verify API Client");
59 | CONFIG_PROPERTIES.add(property);
60 |
61 | property = new ProviderConfigProperty();
62 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
63 | property.setLabel("API Client Secret");
64 | property.setType(ProviderConfigProperty.STRING_TYPE);
65 | property.setHelpText("Client Secret from your IBM Security Verifyl API Client");
66 | property.setSecret(true);
67 | CONFIG_PROPERTIES.add(property);
68 | }
69 |
70 | private Logger logger = Logger.getLogger(IBMSecurityVerifyQrLoginAuthenticatorFactory.class);
71 |
72 | public void close() {
73 | // no-op
74 | }
75 |
76 | public Authenticator create(KeycloakSession session) {
77 | final String methodName = "create";
78 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
79 | return SINGLETON;
80 | }
81 |
82 | public List getConfigProperties() {
83 | // TODO Auto-generated method stub
84 | return CONFIG_PROPERTIES;
85 | }
86 |
87 | public String getDisplayType() {
88 | return "IBM Security Verify QR Login Authenticator";
89 | }
90 |
91 | public String getHelpText() {
92 | return "Login by scanning a QR code with your IBM Verify Mobile App";
93 | }
94 |
95 | public String getId() {
96 | return ID;
97 | }
98 |
99 | public String getReferenceCategory() {
100 | return null;
101 | }
102 |
103 | public Requirement[] getRequirementChoices() {
104 | return REQUIREMENT_CHOICES;
105 | }
106 |
107 | public void init(Scope config) {
108 | // no-op
109 | }
110 |
111 | public boolean isConfigurable() {
112 | return true;
113 | }
114 |
115 | public boolean isUserSetupAllowed() {
116 | return false;
117 | }
118 |
119 | public void postInit(KeycloakSessionFactory factory) {
120 | // no-op
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/webauthn/fido2/IBMSecurityVerifyFido2LoginAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.webauthn.fido2;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.Config.Scope;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.authentication.AuthenticatorFactory;
23 | import org.keycloak.models.AuthenticationExecutionModel;
24 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
25 | import org.keycloak.models.KeycloakSession;
26 | import org.keycloak.models.KeycloakSessionFactory;
27 | import org.keycloak.provider.ProviderConfigProperty;
28 |
29 | import com.ibm.security.verify.authenticator.otp.IBMSecurityVerifyOtpLoginAuthenticator;
30 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
31 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
32 |
33 | public class IBMSecurityVerifyFido2LoginAuthenticatorFactory implements AuthenticatorFactory {
34 |
35 | public static final String ID = "fido2-login-authenticator";
36 | private static final IBMSecurityVerifyFido2LoginAuthenticator SINGLETON = new IBMSecurityVerifyFido2LoginAuthenticator();
37 |
38 | private static final List CONFIG_PROPERTIES = new ArrayList();
39 |
40 | private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
41 | AuthenticationExecutionModel.Requirement.REQUIRED,
42 | AuthenticationExecutionModel.Requirement.ALTERNATIVE
43 | };
44 |
45 | static {
46 | ProviderConfigProperty property;
47 |
48 | property = new ProviderConfigProperty();
49 | property.setName(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
50 | property.setLabel("Tenant Fully Qualified Domain Name");
51 | property.setType(ProviderConfigProperty.STRING_TYPE);
52 | property.setHelpText("The FQDN of your IBM Security Verify tenant");
53 | CONFIG_PROPERTIES.add(property);
54 |
55 | property = new ProviderConfigProperty();
56 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
57 | property.setLabel("API Client ID");
58 | property.setType(ProviderConfigProperty.STRING_TYPE);
59 | property.setHelpText("Client ID from your IBM Security Verify API Client");
60 | CONFIG_PROPERTIES.add(property);
61 |
62 | property = new ProviderConfigProperty();
63 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
64 | property.setLabel("API Client Secret");
65 | property.setType(ProviderConfigProperty.STRING_TYPE);
66 | property.setHelpText("Client Secret from your IBM Security Verifyl API Client");
67 | property.setSecret(true);
68 | CONFIG_PROPERTIES.add(property);
69 | }
70 |
71 | private Logger logger = Logger.getLogger(IBMSecurityVerifyOtpLoginAuthenticator.class);
72 |
73 | public void close() {
74 | // no-op
75 | }
76 |
77 | public Authenticator create(KeycloakSession session) {
78 | final String methodName = "create";
79 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
80 | return SINGLETON;
81 | }
82 |
83 | public List getConfigProperties() {
84 | // TODO Auto-generated method stub
85 | return CONFIG_PROPERTIES;
86 | }
87 |
88 | public String getDisplayType() {
89 | return "IBM Security Verify FIDO2 Login Authenticator";
90 | }
91 |
92 | public String getHelpText() {
93 | return "Login using your FIDO2 device";
94 | }
95 |
96 | public String getId() {
97 | return ID;
98 | }
99 |
100 | public String getReferenceCategory() {
101 | return null;
102 | }
103 |
104 | public Requirement[] getRequirementChoices() {
105 | return REQUIREMENT_CHOICES;
106 | }
107 |
108 | public void init(Scope config) {
109 | // no-op
110 | }
111 |
112 | public boolean isConfigurable() {
113 | return true;
114 | }
115 |
116 | public boolean isUserSetupAllowed() {
117 | return false;
118 | }
119 |
120 | public void postInit(KeycloakSessionFactory factory) {
121 | // no-op
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/verify/push/IBMSecurityVerifyPushNotificationLoginAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.verify.push;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.Config.Scope;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.authentication.AuthenticatorFactory;
23 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
24 | import org.keycloak.models.AuthenticationExecutionModel;
25 | import org.keycloak.models.KeycloakSession;
26 | import org.keycloak.models.KeycloakSessionFactory;
27 | import org.keycloak.provider.ProviderConfigProperty;
28 |
29 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 | import com.ibm.security.verify.authenticator.verify.push.IBMSecurityVerifyPushNotificationLoginAuthenticator;
32 |
33 | public class IBMSecurityVerifyPushNotificationLoginAuthenticatorFactory implements AuthenticatorFactory {
34 |
35 | public static final String ID = "push-login-authenticator";
36 | private static final IBMSecurityVerifyPushNotificationLoginAuthenticator SINGLETON = new IBMSecurityVerifyPushNotificationLoginAuthenticator();
37 |
38 | private static final List CONFIG_PROPERTIES = new ArrayList();
39 |
40 | private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
41 | AuthenticationExecutionModel.Requirement.REQUIRED,
42 | AuthenticationExecutionModel.Requirement.ALTERNATIVE
43 | };
44 |
45 | static {
46 | ProviderConfigProperty property;
47 |
48 | property = new ProviderConfigProperty();
49 | property.setName(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
50 | property.setLabel("Tenant Fully Qualified Domain Name");
51 | property.setType(ProviderConfigProperty.STRING_TYPE);
52 | property.setHelpText("The FQDN of your IBM Security Verify tenant");
53 | CONFIG_PROPERTIES.add(property);
54 |
55 | property = new ProviderConfigProperty();
56 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
57 | property.setLabel("API Client ID");
58 | property.setType(ProviderConfigProperty.STRING_TYPE);
59 | property.setHelpText("Client ID from your IBM Security Verify API Client");
60 | CONFIG_PROPERTIES.add(property);
61 |
62 | property = new ProviderConfigProperty();
63 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
64 | property.setLabel("API Client Secret");
65 | property.setType(ProviderConfigProperty.STRING_TYPE);
66 | property.setHelpText("Client Secret from your IBM Security Verifyl API Client");
67 | property.setSecret(true);
68 | CONFIG_PROPERTIES.add(property);
69 | }
70 |
71 | private Logger logger = Logger.getLogger(IBMSecurityVerifyPushNotificationLoginAuthenticatorFactory.class);
72 |
73 | public void close() {
74 | // no-op
75 | }
76 |
77 | public Authenticator create(KeycloakSession session) {
78 | final String methodName = "create";
79 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
80 | return SINGLETON;
81 | }
82 |
83 | public List getConfigProperties() {
84 | // TODO Auto-generated method stub
85 | return CONFIG_PROPERTIES;
86 | }
87 |
88 | public String getDisplayType() {
89 | return "IBM Security Verify Push Notification Login Authenticator";
90 | }
91 |
92 | public String getHelpText() {
93 | return "Send a push notification to your IBM Verify Mobile App";
94 | }
95 |
96 | public String getId() {
97 | return ID;
98 | }
99 |
100 | public String getReferenceCategory() {
101 | return null;
102 | }
103 |
104 | public Requirement[] getRequirementChoices() {
105 | return REQUIREMENT_CHOICES;
106 | }
107 |
108 | public void init(Scope config) {
109 | // no-op
110 | }
111 |
112 | public boolean isConfigurable() {
113 | return true;
114 | }
115 |
116 | public boolean isUserSetupAllowed() {
117 | return false;
118 | }
119 |
120 | public void postInit(KeycloakSessionFactory factory) {
121 | // no-op
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/AbstractIBMSecurityVerifyAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 IBM
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.Config.Scope;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.authentication.AuthenticatorFactory;
23 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
24 | import org.keycloak.models.KeycloakSession;
25 | import org.keycloak.models.KeycloakSessionFactory;
26 | import org.keycloak.provider.ProviderConfigProperty;
27 |
28 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
29 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
30 |
31 | abstract public class AbstractIBMSecurityVerifyAuthenticatorFactory implements AuthenticatorFactory {
32 |
33 | private static final List CONFIG_PROPERTIES = new ArrayList();
34 |
35 | static {
36 | ProviderConfigProperty property;
37 |
38 | property = new ProviderConfigProperty();
39 | property.setName(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
40 | property.setLabel("Tenant Fully Qualified Domain Name");
41 | property.setType(ProviderConfigProperty.STRING_TYPE);
42 | property.setHelpText("The FQDN of your IBM Security Verify tenant");
43 | CONFIG_PROPERTIES.add(property);
44 |
45 | property = new ProviderConfigProperty();
46 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
47 | property.setLabel("API Client ID");
48 | property.setType(ProviderConfigProperty.STRING_TYPE);
49 | property.setHelpText("Client ID from your IBM Security Verify API Client");
50 | CONFIG_PROPERTIES.add(property);
51 |
52 | property = new ProviderConfigProperty();
53 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
54 | property.setLabel("API Client Secret");
55 | property.setType(ProviderConfigProperty.STRING_TYPE);
56 | property.setHelpText("Client Secret from your IBM Security Verifyl API Client");
57 | property.setSecret(true);
58 | CONFIG_PROPERTIES.add(property);
59 | }
60 |
61 | private Logger logger = Logger.getLogger(AbstractIBMSecurityVerifyAuthenticatorFactory.class);
62 |
63 | public void close() {
64 | // no-op
65 | final String methodName = "close";
66 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
67 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
68 | }
69 |
70 | abstract public Authenticator create(KeycloakSession session);
71 |
72 | public List getConfigProperties() {
73 | final String methodName = "getConfigProperties";
74 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
75 |
76 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, CONFIG_PROPERTIES);
77 | return CONFIG_PROPERTIES;
78 | }
79 |
80 | abstract public String getDisplayType();
81 |
82 | abstract public String getHelpText();
83 |
84 | abstract public String getId();
85 |
86 | public String getReferenceCategory() {
87 | final String methodName = "getReferenceCategory";
88 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
89 |
90 | String referenceCategory = null;
91 |
92 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, referenceCategory);
93 | return referenceCategory;
94 | }
95 |
96 | abstract public Requirement[] getRequirementChoices();
97 |
98 | public void init(Scope config) {
99 | // no-op
100 | final String methodName = "init";
101 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, config);
102 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
103 | }
104 |
105 | public boolean isConfigurable() {
106 | final String methodName = "isConfigurable";
107 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
108 |
109 | boolean isConfigurable = true;
110 |
111 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, isConfigurable);
112 | return isConfigurable;
113 | }
114 |
115 | public boolean isUserSetupAllowed() {
116 | final String methodName = "isUserSetupAllowed";
117 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
118 |
119 | boolean isUserSetupAllowed = false;
120 |
121 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, isUserSetupAllowed);
122 | return isUserSetupAllowed;
123 | }
124 |
125 | public void postInit(KeycloakSessionFactory factory) {
126 | // no-op
127 | final String methodName = "postInit";
128 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, factory);
129 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/webauthn/fido2/IBMSecurityVerifyFido2LoginAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.webauthn.fido2;
15 |
16 | import javax.ws.rs.core.MultivaluedMap;
17 | import javax.ws.rs.core.Response;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.authentication.AuthenticationFlowContext;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.models.KeycloakSession;
23 | import org.keycloak.models.RealmModel;
24 | import org.keycloak.models.UserModel;
25 | import org.keycloak.models.utils.FormMessage;
26 |
27 | import com.ibm.security.verify.authenticator.rest.FormUtilities;
28 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
29 | import com.ibm.security.verify.authenticator.rest.FidoUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 | import com.ibm.security.verify.authenticator.webauthn.registration.IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator;
32 |
33 | public class IBMSecurityVerifyFido2LoginAuthenticator implements Authenticator {
34 |
35 | private static final String FIDO_LOGIN_PAGE_TEMPLATE = "fido-login.ftl";
36 |
37 | private static final String ACTION_PARAM = "action";
38 | private static final String AUTHENTICATE_PARAM = "authenticate";
39 | private static final String REGISTER_ACTION = "register";
40 |
41 | private static final String FIDO_REG_INIT_MACRO = "fidoRegInit";
42 | private static final String FIDO_AUTHN_INIT_MACRO = "fidoAuthnInit";
43 |
44 | private static final String FIDO_HIDE_REG_BUTTON = "fidoHideRegButton";
45 |
46 | private Logger logger = Logger.getLogger(IBMSecurityVerifyFido2LoginAuthenticator.class);
47 |
48 | public void action(AuthenticationFlowContext context) {
49 | final String methodName = "action";
50 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
51 |
52 | MultivaluedMap formParams = context.
53 | getHttpRequest().getDecodedFormParameters();
54 | String action= formParams.getFirst(ACTION_PARAM);
55 |
56 | if (REGISTER_ACTION.equals(action)) {
57 | // Redirect user to FIDO2 Registration (or next flow)
58 | context.attempted();
59 | return;
60 | }
61 |
62 | if (AUTHENTICATE_PARAM.equals(action)) {
63 | String userId = FidoUtilities.completeFidoAuthentication(context);
64 | if (userId != null) {
65 | UserModel user = IBMSecurityVerifyUtilities.matchCIUserIdToUserModel(context, userId);
66 | if (user != null) {
67 | context.setUser(user);
68 | context.success();
69 | } else {
70 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgUserDoesNotExist")));
71 | return;
72 | }
73 | } else {
74 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgUserDoesNotExist")));
75 | return;
76 | }
77 | } else {
78 | context.failure(null);
79 | }
80 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
81 | }
82 |
83 | public void authenticate(AuthenticationFlowContext context) {
84 | final String methodName = "authenticate";
85 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
86 |
87 | String fidoInitAuthnResponse = FidoUtilities.getFidoInitAuthnPayload(context);
88 | if (fidoInitAuthnResponse == null) {
89 | fidoInitAuthnResponse = FidoUtilities.initiateFidoAuthn(context, null);
90 | FidoUtilities.setFidoInitAuthnPayload(context, fidoInitAuthnResponse);
91 | }
92 |
93 | Response challenge = context.form()
94 | .setAttribute(FIDO_REG_INIT_MACRO, "{}")
95 | .setAttribute(FIDO_AUTHN_INIT_MACRO, fidoInitAuthnResponse)
96 | .setAttribute(FIDO_HIDE_REG_BUTTON,
97 | (context.getSession().getAttribute(
98 | IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator.FIDO_REG_VERIFIED)) != null ? true : false)
99 | .createForm(FIDO_LOGIN_PAGE_TEMPLATE);
100 | context.forceChallenge(challenge);
101 |
102 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
103 | }
104 |
105 | public void close() {
106 | // No-op
107 | }
108 |
109 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
110 | // Hardcode to true for the time being
111 | // Only users with verify configured should use this authenticator
112 | return true;
113 | }
114 |
115 | public boolean requiresUser() {
116 | // Doesn't require a user because the user will not yet have been authenticated
117 | return false;
118 | }
119 |
120 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
121 | // No-op for the time being
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/verify/qr/IBMSecurityVerifyQrLoginAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.verify.qr;
15 |
16 | import javax.ws.rs.core.MultivaluedMap;
17 | import javax.ws.rs.core.Response;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.authentication.AuthenticationFlowContext;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.models.KeycloakSession;
23 | import org.keycloak.models.RealmModel;
24 | import org.keycloak.models.UserModel;
25 | import org.keycloak.models.utils.FormMessage;
26 |
27 | import com.ibm.security.verify.authenticator.rest.FormUtilities;
28 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
29 | import com.ibm.security.verify.authenticator.rest.QrUtilities;
30 | import com.ibm.security.verify.authenticator.rest.QrUtilities.QrLoginInitiationResponse;
31 | import com.ibm.security.verify.authenticator.rest.QrUtilities.QrLoginResponse;
32 | import com.ibm.security.verify.authenticator.verify.registration.IBMSecurityVerifyRegistrationRequiredActionAuthenticator;
33 |
34 | public class IBMSecurityVerifyQrLoginAuthenticator implements Authenticator {
35 |
36 | private static final String QR_LOGIN_TEMPLATE = "qr-login.ftl";
37 |
38 | private static final String QR_CODE_ATTR_NAME = "qrCode";
39 |
40 | private static final String ACTION_PARAM = "action";
41 | private static final String VERIFY_REG_BTN = "hideBtn";
42 | private static final String AUTHENTICATE_PARAM = "authenticate";
43 | private static final String REGISTER_ACTION = "register";
44 |
45 | private static final String VERIFY_HIDE_REG_BUTTON = "verifyHideRegButton";
46 |
47 | private static Logger logger = Logger.getLogger(IBMSecurityVerifyQrLoginAuthenticator.class);
48 |
49 | public void action(AuthenticationFlowContext context) {
50 | MultivaluedMap formParams = context.
51 | getHttpRequest().getDecodedFormParameters();
52 | String action= formParams.getFirst(ACTION_PARAM);
53 | String verifyRegVerified = formParams.getFirst(VERIFY_REG_BTN);
54 |
55 | if (REGISTER_ACTION.equals(action)) {
56 | // Redirect user to IBM Verify Registration (or next flow)
57 | context.attempted();
58 | return;
59 | }
60 |
61 | // Poll for the QR login
62 | String qrLoginId = QrUtilities.getQrLoginId(context);
63 | String qrLoginDsi = QrUtilities.getQrLoginDsi(context);
64 | String qrLoginImage = QrUtilities.getQrLoginImage(context);
65 | QrLoginResponse qrResponse = QrUtilities.
66 | pollQrLoginStatus(context, qrLoginId, qrLoginDsi);
67 |
68 | if (AUTHENTICATE_PARAM.equals(action) &&
69 | "SUCCESS".equals(qrResponse.state) && qrResponse.userId != null) {
70 | UserModel user = IBMSecurityVerifyUtilities.matchCIUserIdToUserModel(context, qrResponse.userId);
71 | if (user != null) {
72 | context.setUser(user);
73 | context.success();
74 | } else {
75 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgUserDoesNotExist")));
76 | return;
77 | }
78 | } else if (AUTHENTICATE_PARAM.equals(action) && "FAILED".equals(qrResponse.state)) {
79 | // Attempted but authentication failed (not registered with IBM Verify)
80 | Response challenge = context.form()
81 | .setAttribute(QR_CODE_ATTR_NAME, qrLoginImage)
82 | .addError(new FormMessage("qrVerifyRegistrationRequiredError"))
83 | .createForm(QR_LOGIN_TEMPLATE);
84 | context.challenge(challenge);
85 | } else if (AUTHENTICATE_PARAM.equals(action) && "TIMEOUT".equals(qrResponse.state)) {
86 | context.form().addError(new FormMessage("qrFormLoginTimeOutError"));
87 | authenticate(context);
88 | } else if (AUTHENTICATE_PARAM.equals(action) && "PENDING".equals(qrResponse.state)) {
89 | Response challenge = context.form()
90 | .setAttribute(QR_CODE_ATTR_NAME, qrLoginImage)
91 | .setAttribute(VERIFY_HIDE_REG_BUTTON, Boolean.parseBoolean(verifyRegVerified))
92 | .createForm(QR_LOGIN_TEMPLATE);
93 | context.challenge(challenge);
94 | } else {
95 | // CANCELED
96 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgLoginCanceled")));
97 | return;
98 | }
99 | }
100 |
101 | public void authenticate(AuthenticationFlowContext context) {
102 | // Initiate a QR Login and send the QR code back to the browser
103 | QrLoginInitiationResponse qrResponse = QrUtilities.initiateQrLogin(context);
104 | QrUtilities.setQrLoginId(context, qrResponse.id);
105 | QrUtilities.setQrLoginDsi(context, qrResponse.dsi);
106 | QrUtilities.setQrLoginImage(context, qrResponse.qrBase64Content);
107 | Response challenge = context.form()
108 | .setAttribute(QR_CODE_ATTR_NAME, qrResponse.qrBase64Content)
109 | .setAttribute(VERIFY_HIDE_REG_BUTTON,
110 | (context.getSession().getAttribute(
111 | IBMSecurityVerifyRegistrationRequiredActionAuthenticator.VERIFY_REG_VERIFIED)) != null ? true : false)
112 | .createForm(QR_LOGIN_TEMPLATE);
113 | context.challenge(challenge);
114 | }
115 |
116 | public void close() {
117 | // No-op
118 | }
119 |
120 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
121 | // Hardcode to true for the time being
122 | // Only users with verify configured should use this authenticator
123 | return true;
124 | }
125 |
126 | public boolean requiresUser() {
127 | // Doesn't require a user because the user will not yet have been authenticated
128 | return false;
129 | }
130 |
131 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
132 | // No-op for the time being
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/webauthn/registration/IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.webauthn.registration;
15 |
16 | import javax.ws.rs.core.MultivaluedMap;
17 | import javax.ws.rs.core.Response;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.authentication.AuthenticationFlowContext;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.models.KeycloakSession;
23 | import org.keycloak.models.RealmModel;
24 | import org.keycloak.models.UserModel;
25 | import org.keycloak.models.utils.FormMessage;
26 |
27 | import com.ibm.security.verify.authenticator.rest.FormUtilities;
28 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
29 | import com.ibm.security.verify.authenticator.rest.FidoUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 |
32 | public class IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator implements Authenticator {
33 | private static final String FIDO_REGISTRATION_TEMPLATE = "fido-registration.ftl";
34 |
35 | private static final String ACTION_PARAM = "action";
36 | private static final String REGISTER_ACTION = "register";
37 |
38 | private static final String FIDO_REG_INIT_ATTR_NAME = "fidoRegInit";
39 | private static final String FIDO_AUTHN_INIT_ATTR_NAME = "fidoAuthnInit";
40 |
41 | public static final String FIDO_REG_VERIFIED = "fido.registration.verified";
42 |
43 | private Logger logger = Logger.getLogger(IBMSecurityVerifyFidoRegistrationRequiredActionAuthenticator.class);
44 |
45 | public void action(AuthenticationFlowContext context) {
46 | final String methodName = "action";
47 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
48 |
49 | MultivaluedMap formParams = context.getHttpRequest().getDecodedFormParameters();
50 | String action= formParams.getFirst(ACTION_PARAM);
51 | if (REGISTER_ACTION.equals(action)) {
52 | boolean result = FidoUtilities.completeFidoRegistration(context);
53 | if (result) {
54 | context.form().addSuccess(new FormMessage("fidoRegistrationVerified"));
55 | context.resetFlow();
56 | return;
57 | } else {
58 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgDeviceRegistrationFailed")));
59 | }
60 | } else {
61 | // No-op
62 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgDeviceRegistrationFailed")));
63 | }
64 |
65 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
66 | }
67 |
68 | public void authenticate(AuthenticationFlowContext context) {
69 | final String methodName = "authenticate";
70 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
71 |
72 | UserModel user = context.getUser();
73 | if (user != null) {
74 | // User is associated with the context
75 | String userId = IBMSecurityVerifyUtilities.getCIUserId(context, user);
76 | if (userId == null) {
77 | // User does not yet have a CI user record associated with them. Let's create it now
78 | boolean createdShadowUserSuccessfully = IBMSecurityVerifyUtilities.createCIShadowUser(context, user);
79 | if (createdShadowUserSuccessfully) {
80 | userId = IBMSecurityVerifyUtilities.getCIUserId(context, user);
81 | } else {
82 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgUserRegistrationFailed")));
83 | return;
84 | }
85 | }
86 | if (userId != null) {
87 | // User has a CI User ID
88 | boolean isRegistered = FidoUtilities.doesUserHaveFidoRegistered(context, userId);
89 | if (!isRegistered) {
90 | // User does not have a FIDO authn device registered
91 | String fidoInitRegResp = FidoUtilities.initiateFidoRegistration(context, userId);
92 | Response challenge = context.form()
93 | .setAttribute(FIDO_REG_INIT_ATTR_NAME, fidoInitRegResp)
94 | .setAttribute(FIDO_AUTHN_INIT_ATTR_NAME, "{}")
95 | .createForm(FIDO_REGISTRATION_TEMPLATE);
96 | context.challenge(challenge);
97 |
98 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
99 | return;
100 | } else {
101 | context.form().addSuccess(new FormMessage("fidoRegistrationVerified"));
102 | context.getSession().setAttribute(FIDO_REG_VERIFIED, true);
103 | context.resetFlow();
104 | return;
105 | }
106 | }
107 | } else {
108 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgMissingEmailAndPhoneNumber")));
109 | return;
110 | }
111 |
112 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
113 | }
114 |
115 | public void close() {
116 | final String methodName = "close";
117 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
118 | // no-op
119 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
120 | }
121 |
122 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
123 | final String methodName = "configuredFor";
124 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
125 |
126 | boolean configuredFor = true;
127 |
128 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, configuredFor);
129 | return configuredFor;
130 | }
131 |
132 | public boolean requiresUser() {
133 | final String methodName = "requiresUser";
134 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
135 |
136 | boolean requiresUser = true;
137 |
138 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, requiresUser);
139 | return requiresUser;
140 | }
141 |
142 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
143 | final String methodName = "setRequiredActions";
144 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
145 | // no-op
146 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/demo/IBMSecurityVerifyDemoLoginAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.demo;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.Config.Scope;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.authentication.AuthenticatorFactory;
23 | import org.keycloak.models.AuthenticationExecutionModel;
24 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
25 | import org.keycloak.models.KeycloakSession;
26 | import org.keycloak.models.KeycloakSessionFactory;
27 | import org.keycloak.provider.ProviderConfigProperty;
28 |
29 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 |
32 | public class IBMSecurityVerifyDemoLoginAuthenticatorFactory implements AuthenticatorFactory {
33 |
34 | public static final String ID = "demo-authenticator";
35 | private static final IBMSecurityVerifyDemoLoginAuthenticator SINGLETON = new IBMSecurityVerifyDemoLoginAuthenticator();
36 |
37 | private static final List CONFIG_PROPERTIES = new ArrayList();
38 |
39 | private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
40 | AuthenticationExecutionModel.Requirement.ALTERNATIVE,
41 | AuthenticationExecutionModel.Requirement.REQUIRED,
42 | };
43 |
44 | static {
45 | ProviderConfigProperty property;
46 |
47 | property = new ProviderConfigProperty();
48 | property.setName(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
49 | property.setLabel("Tenant Fully Qualified Domain Name");
50 | property.setType(ProviderConfigProperty.STRING_TYPE);
51 | property.setHelpText("The FQDN of your IBM Security Verify tenant");
52 | CONFIG_PROPERTIES.add(property);
53 |
54 | property = new ProviderConfigProperty();
55 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
56 | property.setLabel("API Client ID");
57 | property.setType(ProviderConfigProperty.STRING_TYPE);
58 | property.setHelpText("Client ID from your IBM Security Verify API Client");
59 | CONFIG_PROPERTIES.add(property);
60 |
61 | property = new ProviderConfigProperty();
62 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
63 | property.setLabel("API Client Secret");
64 | property.setType(ProviderConfigProperty.STRING_TYPE);
65 | property.setHelpText("Client Secret from your IBM Security Verifyl API Client");
66 | property.setSecret(true);
67 | CONFIG_PROPERTIES.add(property);
68 | }
69 |
70 | private Logger logger = Logger.getLogger(IBMSecurityVerifyDemoLoginAuthenticatorFactory.class);
71 |
72 | public void close() {
73 | final String methodName = "close";
74 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
75 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
76 | }
77 |
78 | public Authenticator create(KeycloakSession session) {
79 | final String methodName = "create";
80 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
81 | return SINGLETON;
82 | }
83 |
84 | public List getConfigProperties() {
85 | final String methodName = "getConfigProperties";
86 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
87 |
88 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, CONFIG_PROPERTIES);
89 | return CONFIG_PROPERTIES;
90 | }
91 |
92 | public String getDisplayType() {
93 | final String methodName = "getDisplayType";
94 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
95 |
96 | String displayType = "IBM Security Verify Demo Authenticator";
97 |
98 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, displayType);
99 | return displayType;
100 | }
101 |
102 | public String getHelpText() {
103 | final String methodName = "getHelpText";
104 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
105 |
106 | String helpText = "IBM Security Verify Demo Authenticator help text";
107 |
108 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, helpText);
109 | return helpText;
110 | }
111 |
112 | public String getId() {
113 | final String methodName = "getId";
114 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
115 |
116 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, ID);
117 | return ID;
118 | }
119 |
120 | public String getReferenceCategory() {
121 | final String methodName = "getReferenceCategory";
122 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
123 |
124 | String referenceCategory = null;
125 |
126 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, referenceCategory);
127 | return referenceCategory;
128 | }
129 |
130 | public Requirement[] getRequirementChoices() {
131 | final String methodName = "getRequirementChoices";
132 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
133 |
134 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, REQUIREMENT_CHOICES);
135 | return REQUIREMENT_CHOICES;
136 | }
137 |
138 | public void init(Scope config) {
139 | final String methodName = "init";
140 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, config);
141 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
142 | }
143 |
144 | public boolean isConfigurable() {
145 | final String methodName = "isConfigurable";
146 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
147 |
148 | boolean isConfigurable = true;
149 |
150 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, isConfigurable);
151 | return isConfigurable;
152 | }
153 |
154 | public boolean isUserSetupAllowed() {
155 | final String methodName = "isUserSetupAllowed";
156 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
157 |
158 | boolean isUserSetupAllowed = false;
159 |
160 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, isUserSetupAllowed);
161 | return isUserSetupAllowed;
162 | }
163 |
164 | public void postInit(KeycloakSessionFactory factory) {
165 | final String methodName = "postInit";
166 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
167 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/otp/IBMSecurityVerifyOtpLoginAuthenticatorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.otp;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.Config.Scope;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.authentication.AuthenticatorFactory;
23 | import org.keycloak.models.AuthenticationExecutionModel;
24 | import org.keycloak.models.AuthenticationExecutionModel.Requirement;
25 | import org.keycloak.models.KeycloakSession;
26 | import org.keycloak.models.KeycloakSessionFactory;
27 | import org.keycloak.provider.ProviderConfigProperty;
28 |
29 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 |
32 | public class IBMSecurityVerifyOtpLoginAuthenticatorFactory implements AuthenticatorFactory {
33 |
34 | public static final String ID = "otp-authenticator";
35 | private static final IBMSecurityVerifyOtpLoginAuthenticator SINGLETON = new IBMSecurityVerifyOtpLoginAuthenticator();
36 |
37 | private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
38 | AuthenticationExecutionModel.Requirement.REQUIRED,
39 | AuthenticationExecutionModel.Requirement.DISABLED
40 | };
41 |
42 | private static final List CONFIG_PROPERTIES = new ArrayList();
43 |
44 | static {
45 | ProviderConfigProperty property;
46 |
47 | property = new ProviderConfigProperty();
48 | property.setName(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
49 | property.setLabel("Tenant Fully Qualified Domain Name");
50 | property.setType(ProviderConfigProperty.STRING_TYPE);
51 | property.setHelpText("The FQDN of your IBM Security Verify tenant");
52 | CONFIG_PROPERTIES.add(property);
53 |
54 | property = new ProviderConfigProperty();
55 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
56 | property.setLabel("API Client ID");
57 | property.setType(ProviderConfigProperty.STRING_TYPE);
58 | property.setHelpText("Client ID from your IBM Security Verify API Client");
59 | CONFIG_PROPERTIES.add(property);
60 |
61 | property = new ProviderConfigProperty();
62 | property.setName(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
63 | property.setLabel("API Client Secret");
64 | property.setType(ProviderConfigProperty.STRING_TYPE);
65 | property.setHelpText("Client Secret from your IBM Security Verifyl API Client");
66 | property.setSecret(true);
67 | CONFIG_PROPERTIES.add(property);
68 | }
69 |
70 | private Logger logger = Logger.getLogger(IBMSecurityVerifyOtpLoginAuthenticatorFactory.class);
71 |
72 | public void close() {
73 | final String methodName = "close";
74 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
75 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
76 | }
77 |
78 | public Authenticator create(KeycloakSession session) {
79 | final String methodName = "create";
80 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session);
81 | return SINGLETON;
82 | }
83 |
84 | public List getConfigProperties() {
85 | final String methodName = "getConfigProperties";
86 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
87 |
88 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, CONFIG_PROPERTIES);
89 | return CONFIG_PROPERTIES;
90 | }
91 |
92 | public String getDisplayType() {
93 | final String methodName = "getDisplayType";
94 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
95 | logger.tracef("%s entry", methodName);
96 |
97 | String displayType = "IBM Security Verify OTP Authenticator";
98 |
99 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, displayType);
100 | return displayType;
101 | }
102 |
103 | public String getHelpText() {
104 | final String methodName = "getHelpText";
105 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
106 |
107 | String helpText = "Send a one time password via Email or SMS";
108 |
109 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, helpText);
110 | return helpText;
111 | }
112 |
113 | public String getId() {
114 | final String methodName = "getId";
115 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
116 |
117 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, ID);
118 | return ID;
119 | }
120 |
121 | public String getReferenceCategory() {
122 | final String methodName = "getReferenceCategory";
123 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
124 |
125 | String referenceCategory = null;
126 |
127 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, referenceCategory);
128 | return referenceCategory;
129 | }
130 |
131 | public Requirement[] getRequirementChoices() {
132 | final String methodName = "getRequirementChoices";
133 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
134 |
135 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, REQUIREMENT_CHOICES);
136 | return REQUIREMENT_CHOICES;
137 | }
138 |
139 | public void init(Scope scope) {
140 | final String methodName = "init";
141 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, scope);
142 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
143 | }
144 |
145 | public boolean isConfigurable() {
146 | final String methodName = "isConfigurable";
147 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
148 |
149 | boolean isConfigurable = true;
150 |
151 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, isConfigurable);
152 | return isConfigurable;
153 | }
154 |
155 | public boolean isUserSetupAllowed() {
156 | final String methodName = "isUserSetupAllowed";
157 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
158 |
159 | boolean isUserSetupAllowed = false;
160 |
161 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, isUserSetupAllowed);
162 | return isUserSetupAllowed;
163 | }
164 |
165 | public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
166 | final String methodName = "postInit";
167 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, keycloakSessionFactory);
168 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/verify/registration/IBMSecurityVerifyRegistrationRequiredActionAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.verify.registration;
15 |
16 | import javax.ws.rs.core.MultivaluedMap;
17 | import javax.ws.rs.core.Response;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.authentication.AuthenticationFlowContext;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.models.KeycloakSession;
23 | import org.keycloak.models.RealmModel;
24 | import org.keycloak.models.UserModel;
25 | import org.keycloak.models.utils.FormMessage;
26 |
27 | import com.ibm.security.verify.authenticator.rest.FormUtilities;
28 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
29 | import com.ibm.security.verify.authenticator.rest.VerifyAppUtilities;
30 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
31 |
32 | public class IBMSecurityVerifyRegistrationRequiredActionAuthenticator implements Authenticator {
33 |
34 | private static final String VERIFY_REGISTRATION_TEMPLATE = "verify-registration.ftl";
35 | private static final String QR_CODE_ATTR_NAME = "qrCode";
36 | private static final String VERIFY_REGISTRATION_FRIENDLY_NAME = "Keycloak SSO: %s";
37 |
38 | private static final String ACTION_PARAM = "action";
39 | private static final String REGISTER_ACTION = "register";
40 |
41 | public static final String VERIFY_REG_VERIFIED = "verify.registration.verified";
42 |
43 | private Logger logger = Logger.getLogger(IBMSecurityVerifyRegistrationRequiredActionAuthenticator.class);
44 |
45 | public void action(AuthenticationFlowContext context) {
46 | final String methodName = "action";
47 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
48 |
49 | MultivaluedMap formParams = context.getHttpRequest().getDecodedFormParameters();
50 | String action= formParams.getFirst(ACTION_PARAM);
51 | if (REGISTER_ACTION.equals(action)) {
52 | // User has not yet cancelled the registration attempt. Let's poll for registration status
53 | initiateAndPoll(context);
54 | }
55 |
56 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
57 | }
58 |
59 | public void authenticate(AuthenticationFlowContext context) {
60 | final String methodName = "authenticate";
61 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
62 | initiateAndPoll(context);
63 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
64 | }
65 |
66 | private void initiateAndPoll(AuthenticationFlowContext context) {
67 | final String methodName = "initiateAndPoll";
68 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
69 |
70 | UserModel user = context.getUser();
71 | if (user != null) {
72 | // User is associated with the context
73 | String userId = IBMSecurityVerifyUtilities.getCIUserId(context, user);
74 | if (userId == null) {
75 | // User does not yet have a CI user record associated with them. Let's create it now
76 | if (user.getEmail() == null) {
77 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgMissingEmail")));
78 | return;
79 | }
80 | boolean createdShadowUserSuccessfully = IBMSecurityVerifyUtilities.createCIShadowUser(context, user);
81 | if (createdShadowUserSuccessfully) {
82 | userId = IBMSecurityVerifyUtilities.getCIUserId(context, user);
83 | } else {
84 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgUserRegistrationFailed")));
85 | return;
86 | }
87 | }
88 | if (userId != null) {
89 | // User has a CI User ID
90 | boolean isRegistered = VerifyAppUtilities.doesUserHaveVerifyRegistered(context, userId);
91 | if (!isRegistered) {
92 | // User does not have IBM Verify registered
93 | // Check to see if we've already initiated the verify registration
94 | String qrCode = VerifyAppUtilities.getVerifyRegistrationQrCode(context);
95 | if (qrCode == null) {
96 | // No verify registration initiated yet, let's start it up
97 | qrCode = VerifyAppUtilities.initiateVerifyAuthenticatorRegistration(context, userId,
98 | String.format(VERIFY_REGISTRATION_FRIENDLY_NAME, user.getUsername()));
99 | VerifyAppUtilities.setVerifyRegistrationQrCode(context, qrCode);
100 | }
101 | Response challenge = context.form()
102 | .setAttribute(QR_CODE_ATTR_NAME, qrCode)
103 | .createForm(VERIFY_REGISTRATION_TEMPLATE);
104 | context.challenge(challenge);
105 |
106 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
107 | return;
108 | } else {
109 | context.form().addSuccess(new FormMessage("verifyRegistrationVerified"));
110 | context.getSession().setAttribute(VERIFY_REG_VERIFIED, true);
111 | context.resetFlow();
112 | return;
113 | }
114 | }
115 | } else {
116 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgMissingEmailAndPhoneNumber")));
117 | return;
118 | }
119 |
120 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
121 | }
122 |
123 | public void close() {
124 | final String methodName = "close";
125 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
126 | // no-op
127 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
128 | }
129 |
130 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
131 | final String methodName = "configuredFor";
132 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
133 |
134 | boolean configuredFor = true;
135 |
136 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, configuredFor);
137 | return configuredFor;
138 | }
139 |
140 | public boolean requiresUser() {
141 | final String methodName = "requiresUser";
142 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
143 |
144 | boolean requiresUser = true;
145 |
146 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, requiresUser);
147 | return requiresUser;
148 | }
149 |
150 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
151 | final String methodName = "setRequiredActions";
152 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
153 | // no-op
154 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/verify/push/IBMSecurityVerifyPushNotificationLoginAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.verify.push;
15 |
16 | import javax.ws.rs.core.MultivaluedMap;
17 | import javax.ws.rs.core.Response;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.authentication.AuthenticationFlowContext;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.models.KeycloakSession;
23 | import org.keycloak.models.RealmModel;
24 | import org.keycloak.models.UserModel;
25 | import org.keycloak.models.utils.FormMessage;
26 |
27 | import com.ibm.security.verify.authenticator.rest.FormUtilities;
28 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
29 | import com.ibm.security.verify.authenticator.rest.PushNotificationUtilities;
30 | import com.ibm.security.verify.authenticator.rest.VerifyAppUtilities;
31 | import com.ibm.security.verify.authenticator.rest.PushNotificationUtilities.Pair;
32 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
33 |
34 | public class IBMSecurityVerifyPushNotificationLoginAuthenticator implements Authenticator {
35 |
36 | private static final String PUSH_NOTIFICATION_LOGIN_TEMPLATE = "push-notification-login.ftl";
37 | private static final String PUSH_NOTIFICATION_LOGIN_RESEND_TEMPLATE = "push-notification-login-resend.ftl";
38 |
39 | private static final String ACTION_PARAM = "action";
40 | private static final String AUTHENTICATE_PARAM = "authenticate";
41 | private static final String RESEND_PARAM = "resend";
42 |
43 | private Logger logger = Logger.getLogger(IBMSecurityVerifyPushNotificationLoginAuthenticator.class);
44 |
45 | public void action(AuthenticationFlowContext context) {
46 | MultivaluedMap formParams = context.
47 | getHttpRequest().getDecodedFormParameters();
48 | String action= formParams.getFirst(ACTION_PARAM);
49 |
50 | UserModel user = context.getUser();
51 | if (user == null) {
52 | context.challenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsg2FARequired")));
53 | return;
54 | }
55 |
56 | if (RESEND_PARAM.equals(action)) {
57 | context.getAuthenticationSession().removeAuthNote(
58 | PushNotificationUtilities.PUSH_NOTIFICATION_AUTHENTICATOR_ID_ATTR_NAME);
59 | context.getAuthenticationSession().removeAuthNote(
60 | PushNotificationUtilities.PUSH_NOTIFICATION_TRANSACTION_ID_ATTR_NAME);
61 | initiatePushNotification(context);
62 | return;
63 | }
64 |
65 | String pushNotificationState = PushNotificationUtilities.getPushNotificationVerification(context);
66 |
67 | if (AUTHENTICATE_PARAM.equals(action) &&
68 | "VERIFY_SUCCESS".equals(pushNotificationState)) {
69 | context.success();
70 | return;
71 | } else if (AUTHENTICATE_PARAM.equals(action) && "PENDING".equals(pushNotificationState)) {
72 | Response challenge = context.form()
73 | .createForm(PUSH_NOTIFICATION_LOGIN_TEMPLATE);
74 | context.challenge(challenge);
75 | return;
76 | } else if (AUTHENTICATE_PARAM.equals(action) && "TIMEOUT".equals(pushNotificationState)) {
77 | Response challenge = context.form()
78 | .addError(new FormMessage("pushNotificationFormExpiredError"))
79 | .createForm(PUSH_NOTIFICATION_LOGIN_RESEND_TEMPLATE);
80 | context.forceChallenge(challenge);
81 | } else {
82 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgAccessDenied")));
83 | }
84 | }
85 |
86 | public void authenticate(AuthenticationFlowContext context) {
87 | if (context.getAuthenticationSession().getAuthNote(
88 | PushNotificationUtilities.PUSH_NOTIFICATION_TRANSACTION_ID_ATTR_NAME) == null) {
89 | initiatePushNotification(context);
90 | }
91 | }
92 |
93 | private void initiatePushNotification(AuthenticationFlowContext context) {
94 | final String methodName = "initiatePushNotification";
95 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
96 |
97 | UserModel user = context.getUser();
98 | if (user != null) {
99 | // User is associated with the context
100 | String userId = IBMSecurityVerifyUtilities.getCIUserId(context, user);
101 | if (userId == null) {
102 | requireVerifyRegistration(context, methodName);
103 | return;
104 | } else {
105 | boolean isRegistered = VerifyAppUtilities.doesUserHaveVerifyRegistered(context, userId);
106 | if (!isRegistered) {
107 | requireVerifyRegistration(context, methodName);
108 | return;
109 | } else {
110 | Pair result = PushNotificationUtilities.getSignatureEnrollmentAuthenticatorId(context, userId);
111 | PushNotificationUtilities.sendPushNotification(context, result.key, result.value);
112 |
113 | Response challenge = context.form()
114 | .createForm(PUSH_NOTIFICATION_LOGIN_TEMPLATE);
115 | context.challenge(challenge);
116 |
117 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
118 | return;
119 | }
120 | }
121 | } else {
122 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsg2FARequired")));
123 | }
124 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
125 | }
126 |
127 | private void requireVerifyRegistration(AuthenticationFlowContext context, String methodName) {
128 | context.form().addError(new FormMessage("verifyRegistrationRequired"));
129 | context.attempted();
130 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
131 | }
132 |
133 | public void close() {
134 | // No-op
135 | }
136 |
137 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
138 | // Hardcode to true for the time being
139 | // Only users with verify configured should use this authenticator
140 | return true;
141 | }
142 |
143 | public boolean requiresUser() {
144 | return true;
145 | }
146 |
147 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
148 | // No-op for the time being
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/demo/IBMSecurityVerifyDemoLoginAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.demo;
15 |
16 | import javax.ws.rs.core.MultivaluedMap;
17 | import javax.ws.rs.core.Response;
18 |
19 | import org.jboss.logging.Logger;
20 | import org.keycloak.authentication.AuthenticationFlowContext;
21 | import org.keycloak.authentication.Authenticator;
22 | import org.keycloak.models.KeycloakSession;
23 | import org.keycloak.models.RealmModel;
24 | import org.keycloak.models.UserModel;
25 |
26 | import com.ibm.security.verify.authenticator.rest.IBMSecurityVerifyUtilities;
27 | import com.ibm.security.verify.authenticator.rest.FidoUtilities;
28 | import com.ibm.security.verify.authenticator.rest.QrUtilities;
29 | import com.ibm.security.verify.authenticator.rest.QrUtilities.QrLoginInitiationResponse;
30 | import com.ibm.security.verify.authenticator.rest.QrUtilities.QrLoginResponse;
31 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
32 |
33 | public class IBMSecurityVerifyDemoLoginAuthenticator implements Authenticator {
34 |
35 | /**
36 | * HTML Page template constants
37 | */
38 | private static final String INITIAL_LOGIN_PAGE_TEMPLATE = "demo-login.ftl";
39 |
40 | /**
41 | * HTML Page template injection macros
42 | */
43 | private static final String FIDO_AUTHN_INIT_MACRO = "fidoAuthnInit";
44 | private static final String FIDO_REG_INIT_MACRO = "fidoRegInit";
45 | private static final String QR_AUTHN_INIT_MACRO = "qrAuthnInit";
46 |
47 | private static final String ACTION_TYPE = "action-type";
48 | private static final String ACTION_TYPE_FIDO = "fido";
49 | private static final String ACTION_TYPE_QR = "qr";
50 |
51 | private Logger logger = Logger.getLogger(IBMSecurityVerifyDemoLoginAuthenticator.class);
52 |
53 | public void action(AuthenticationFlowContext context) {
54 | final String methodName = "action";
55 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
56 |
57 | MultivaluedMap formParams = context.getHttpRequest().getDecodedFormParameters();
58 | String actionType = formParams.getFirst(ACTION_TYPE);
59 | if (ACTION_TYPE_FIDO.equals(actionType)) {
60 | String userId = FidoUtilities.completeFidoAuthentication(context);
61 | if (userId != null) {
62 | UserModel user = IBMSecurityVerifyUtilities.matchCIUserIdToUserModel(context, userId);
63 | if (user != null) {
64 | context.setUser(user);
65 | context.success();
66 | } else {
67 | // User ID doesn't match a user in RH-SSO
68 | context.failure(null);
69 | }
70 | } else {
71 | // User ID not obtained from FIDO response
72 | context.failure(null);
73 | }
74 | } else if (ACTION_TYPE_QR.equals(actionType)) {
75 | String qrLoginId = QrUtilities.getQrLoginId(context);
76 | String qrLoginDsi = QrUtilities.getQrLoginDsi(context);
77 | QrLoginResponse qrResponse = QrUtilities.pollQrLoginStatus(context, qrLoginId, qrLoginDsi);
78 | if ("SUCCESS".equals(qrResponse.state) && qrResponse.userId != null) {
79 | UserModel user = IBMSecurityVerifyUtilities.matchCIUserIdToUserModel(context, qrResponse.userId);
80 | if (user != null) {
81 | context.setUser(user);
82 | context.success();
83 | } else {
84 | // User ID doesn't match a user in RH-SSO
85 | context.failure(null);
86 | }
87 | } else {
88 | // Not successful, so this was just a polling interval. Restart the authn flow
89 | authenticate(context);
90 | }
91 | } else {
92 | // Handle bypass flow; treat any rogue input as bypass flow
93 | context.attempted();
94 | }
95 |
96 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
97 | }
98 |
99 | public void authenticate(AuthenticationFlowContext context) {
100 | final String methodName = "authenticate";
101 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
102 | /**
103 | * 0) Check session cache if the FIDO and QR authn has already been initiated. Only initiate on first access
104 | * 1) Initiate QR login flow
105 | * 2) Initiate FIDO login flow
106 | * 3) Send both QR and FIDO login flow data to browser so the user can use either
107 | * 4) Also render a button that will allow a user to do username/password login, bypassing either CIV option
108 | */
109 |
110 | String fidoInitAuthnResponse = FidoUtilities.getFidoInitAuthnPayload(context);
111 | if (fidoInitAuthnResponse == null) {
112 | fidoInitAuthnResponse = FidoUtilities.initiateFidoAuthn(context, null);
113 | FidoUtilities.setFidoInitAuthnPayload(context, fidoInitAuthnResponse);
114 | }
115 |
116 | String qrInitAuthnResponseDsi = QrUtilities.getQrLoginDsi(context);
117 | String qrInitAuthnResponseId = QrUtilities.getQrLoginId(context);
118 | String qrInitAuthnResponseImage = QrUtilities.getQrLoginImage(context);
119 | if (qrInitAuthnResponseDsi == null || qrInitAuthnResponseId == null || qrInitAuthnResponseImage == null) {
120 | QrLoginInitiationResponse qrInitAuthnResponse = QrUtilities.initiateQrLogin(context);
121 | qrInitAuthnResponseDsi = qrInitAuthnResponse.dsi;
122 | qrInitAuthnResponseId = qrInitAuthnResponse.id;
123 | qrInitAuthnResponseImage = qrInitAuthnResponse.qrBase64Content;
124 | QrUtilities.setQrLoginDsi(context, qrInitAuthnResponseDsi);
125 | QrUtilities.setQrLoginId(context, qrInitAuthnResponseId);
126 | QrUtilities.setQrLoginImage(context, qrInitAuthnResponseImage);
127 | }
128 |
129 | Response challenge = context.form()
130 | .setAttribute(FIDO_REG_INIT_MACRO, "{}")
131 | .setAttribute(FIDO_AUTHN_INIT_MACRO, fidoInitAuthnResponse)
132 | .setAttribute(QR_AUTHN_INIT_MACRO, qrInitAuthnResponseImage)
133 | .createForm(INITIAL_LOGIN_PAGE_TEMPLATE);
134 | context.forceChallenge(challenge);
135 |
136 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
137 | }
138 |
139 | public void close() {
140 | final String methodName = "close";
141 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
142 | // No-op
143 | // Token(s) could be revoked here for better cleanup
144 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
145 | }
146 |
147 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
148 | final String methodName = "configuredFor";
149 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
150 |
151 | // Hardcode to true for the time being
152 | boolean configuredFor = true;
153 |
154 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, configuredFor);
155 | return configuredFor;
156 | }
157 |
158 | public boolean requiresUser() {
159 | final String methodName = "requiresUser";
160 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
161 |
162 | boolean requiresUser = false;
163 |
164 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, requiresUser);
165 | return requiresUser;
166 | }
167 |
168 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
169 | final String methodName = "setRequiredActions";
170 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
171 | // No-op for the time being
172 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
173 | }
174 |
175 | }
176 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/fido-login.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
25 |
28 |
31 |
32 |
270 | #if>
271 | @layout.registrationLayout>
272 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/demo-login.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
30 |
274 | #if>
275 | @layout.registrationLayout>
276 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/resources/theme-resources/templates/fido-registration.ftl:
--------------------------------------------------------------------------------
1 | <#--
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | -->
13 |
14 | <#import "template.ftl" as layout>
15 | <@layout.registrationLayout showAnotherWayIfPresent=false; section>
16 | <#if section = "title">
17 | ${msg("loginTitle",realm.name)}
18 | <#elseif section = "header">
19 | ${msg("loginTitleHtml",realm.name)}
20 | <#elseif section = "form">
21 |
22 |
32 |
33 |
36 |
37 |
288 | #if>
289 | @layout.registrationLayout>
290 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/rest/QrUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.rest;
15 |
16 | import java.io.IOException;
17 | import java.net.URI;
18 | import java.net.URISyntaxException;
19 | import java.util.regex.Matcher;
20 | import java.util.regex.Pattern;
21 |
22 | import org.apache.http.client.ClientProtocolException;
23 | import org.apache.http.client.methods.CloseableHttpResponse;
24 | import org.apache.http.client.methods.HttpGet;
25 | import org.apache.http.client.utils.URIBuilder;
26 | import org.apache.http.impl.client.CloseableHttpClient;
27 | import org.apache.http.impl.client.HttpClients;
28 | import org.apache.http.util.EntityUtils;
29 | import org.jboss.logging.Logger;
30 | import org.keycloak.authentication.AuthenticationFlowContext;
31 |
32 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
33 |
34 | public class QrUtilities {
35 |
36 | private static Logger logger = Logger.getLogger(QrUtilities.class);
37 |
38 | public static QrLoginInitiationResponse initiateQrLogin(AuthenticationFlowContext context) {
39 | final String methodName = "initiateQrLogin";
40 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
41 |
42 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
43 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
44 | String verifyProfileId = VerifyAppUtilities.getVerifyProfileId(context);
45 | CloseableHttpClient httpClient = null;
46 | QrLoginInitiationResponse qrResponse = null;
47 | try {
48 | httpClient = HttpClients.createDefault();
49 | URI uri = new URIBuilder()
50 | .setScheme("https")
51 | .setHost(tenantHostname)
52 | .setPath("/v2.0/factors/qr/authenticate")
53 | .setParameter("profileId", verifyProfileId)
54 | .build();
55 | HttpGet getRequest = new HttpGet(uri);
56 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
57 | getRequest.addHeader("Accept", "application/json");
58 | CloseableHttpResponse response = httpClient.execute(getRequest);
59 | int statusCode = response.getStatusLine().getStatusCode();
60 | String responseBody = EntityUtils.toString(response.getEntity());
61 | EntityUtils.consume(response.getEntity());
62 | if (statusCode == 200) {
63 | String qrCode = null;
64 | Pattern qrExtraction = Pattern.compile("\"qrCode\":\\s*\"([^\"]+)\"");
65 | Matcher matcher = qrExtraction.matcher(responseBody);
66 | if (matcher.find()) {
67 | qrCode = matcher.group(1);
68 | }
69 | String id = null;
70 | Pattern idExtraction = Pattern.compile("\"id\":\\s*\"([a-fA-F0-9\\-]+)\"");
71 | matcher = idExtraction.matcher(responseBody);
72 | if (matcher.find()) {
73 | id = matcher.group(1);
74 | }
75 | String dsi = null;
76 | Pattern dsiExtraction = Pattern.compile("\"dsi\":\\s*\"([a-zA-Z0-9]+)\"");
77 | matcher = dsiExtraction.matcher(responseBody);
78 | if (matcher.find()) {
79 | dsi = matcher.group(1);
80 | }
81 | qrResponse = new QrLoginInitiationResponse(qrCode, id, dsi);
82 | } else {
83 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
84 | }
85 | response.close();
86 | } catch (URISyntaxException e) {
87 | e.printStackTrace();
88 | } catch (ClientProtocolException e) {
89 | e.printStackTrace();
90 | } catch (IOException e) {
91 | e.printStackTrace();
92 | } finally {
93 | if (httpClient != null) {
94 | try {
95 | httpClient.close();
96 | } catch (IOException e) {
97 | e.printStackTrace();
98 | }
99 | }
100 | }
101 |
102 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, qrResponse);
103 | return qrResponse;
104 | }
105 |
106 | public static class QrLoginInitiationResponse {
107 | public String qrBase64Content;
108 | public String id;
109 | public String dsi;
110 |
111 | QrLoginInitiationResponse(String qrContent, String id, String dsi) {
112 | this.qrBase64Content = qrContent;
113 | this.id = id;
114 | this.dsi = dsi;
115 | }
116 |
117 | public String toString() {
118 | return "id=[" + id + "] dsi=[" + dsi + "] qrBase64Content=[" + qrBase64Content + "]";
119 | }
120 | }
121 |
122 | public static QrLoginResponse pollQrLoginStatus(AuthenticationFlowContext context, String qrLoginId, String qrLoginDsi) {
123 | final String methodName = "pollQrLoginStatus";
124 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, qrLoginId, qrLoginDsi);
125 |
126 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
127 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
128 | CloseableHttpClient httpClient = null;
129 | QrLoginResponse qrResponse = null;
130 | try {
131 | httpClient = HttpClients.createDefault();
132 | URI uri = new URIBuilder()
133 | .setScheme("https")
134 | .setHost(tenantHostname)
135 | .setPath("/v2.0/factors/qr/authenticate/" + qrLoginId)
136 | .setParameter("dsi", qrLoginDsi)
137 | .build();
138 | HttpGet getRequest = new HttpGet(uri);
139 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
140 | getRequest.addHeader("Accept", "application/json");
141 | CloseableHttpResponse response = httpClient.execute(getRequest);
142 | int statusCode = response.getStatusLine().getStatusCode();
143 | String responseBody = EntityUtils.toString(response.getEntity());
144 | EntityUtils.consume(response.getEntity());
145 | if (statusCode == 200) {
146 | String state = null;
147 | Pattern stateExtraction = Pattern.compile("\"state\":\\s*\"([a-zA-Z]+)\"");
148 | Matcher matcher = stateExtraction.matcher(responseBody);
149 | if (matcher.find()) {
150 | state = matcher.group(1);
151 | }
152 | String userId = null;
153 | Pattern userIdExtraction = Pattern.compile("\"userId\":\\s*\"([a-zA-Z0-9]+)\"");
154 | matcher = userIdExtraction.matcher(responseBody);
155 | if (matcher.find()) {
156 | userId = matcher.group(1);
157 | }
158 | qrResponse = new QrLoginResponse(state, userId);
159 | } else {
160 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
161 | }
162 | response.close();
163 | } catch (URISyntaxException e) {
164 | e.printStackTrace();
165 | } catch (ClientProtocolException e) {
166 | e.printStackTrace();
167 | } catch (IOException e) {
168 | e.printStackTrace();
169 | } finally {
170 | if (httpClient != null) {
171 | try {
172 | httpClient.close();
173 | } catch (IOException e) {
174 | e.printStackTrace();
175 | }
176 | }
177 | }
178 |
179 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, qrResponse);
180 | return qrResponse;
181 | }
182 |
183 | public static class QrLoginResponse {
184 | public String state;
185 | public String userId;
186 |
187 | QrLoginResponse(String state, String userId) {
188 | this.state = state;
189 | this.userId = userId;
190 | }
191 | }
192 |
193 | public static String getQrLoginId(AuthenticationFlowContext context) {
194 | final String methodName = "getQrLoginId";
195 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
196 |
197 | String result = context.getAuthenticationSession().getUserSessionNotes().get("qr.login.id");
198 |
199 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
200 | return result;
201 | }
202 |
203 | public static void setQrLoginId(AuthenticationFlowContext context, String qrLoginId) {
204 | final String methodName = "setQrLoginId";
205 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, qrLoginId);
206 |
207 | context.getAuthenticationSession().setUserSessionNote("qr.login.id", qrLoginId);
208 |
209 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
210 | }
211 |
212 | public static String getQrLoginDsi(AuthenticationFlowContext context) {
213 | final String methodName = "getQrLoginDsi";
214 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
215 |
216 | String result = context.getAuthenticationSession().getUserSessionNotes().get("qr.login.dsi");
217 |
218 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
219 | return result;
220 | }
221 |
222 | public static void setQrLoginDsi(AuthenticationFlowContext context, String qrLoginDsi) {
223 | final String methodName = "setQrLoginDsi";
224 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, qrLoginDsi);
225 |
226 | context.getAuthenticationSession().setUserSessionNote("qr.login.dsi", qrLoginDsi);
227 |
228 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
229 | }
230 |
231 | public static String getQrLoginImage(AuthenticationFlowContext context) {
232 | final String methodName = "getQrLoginImage";
233 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
234 |
235 | String result = context.getAuthenticationSession().getUserSessionNotes().get("qr.login.image");
236 |
237 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
238 | return result;
239 | }
240 |
241 | public static void setQrLoginImage(AuthenticationFlowContext context, String qrLoginImage) {
242 | final String methodName = "setQrLoginImage";
243 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, qrLoginImage);
244 |
245 | context.getAuthenticationSession().setUserSessionNote("qr.login.image", qrLoginImage);
246 |
247 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/rest/VerifyAppUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.rest;
15 |
16 | import java.io.IOException;
17 | import java.net.URI;
18 | import java.net.URISyntaxException;
19 | import java.util.regex.Matcher;
20 | import java.util.regex.Pattern;
21 |
22 | import org.apache.http.client.ClientProtocolException;
23 | import org.apache.http.client.methods.CloseableHttpResponse;
24 | import org.apache.http.client.methods.HttpGet;
25 | import org.apache.http.client.methods.HttpPost;
26 | import org.apache.http.client.utils.URIBuilder;
27 | import org.apache.http.entity.StringEntity;
28 | import org.apache.http.impl.client.CloseableHttpClient;
29 | import org.apache.http.impl.client.HttpClients;
30 | import org.apache.http.util.EntityUtils;
31 | import org.jboss.logging.Logger;
32 | import org.keycloak.authentication.AuthenticationFlowContext;
33 |
34 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
35 |
36 | public class VerifyAppUtilities {
37 |
38 | private static Logger logger = Logger.getLogger(VerifyAppUtilities.class);
39 |
40 | public static String getVerifyProfileId(AuthenticationFlowContext context) {
41 | final String methodName = "getVerifyProfileId";
42 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
43 |
44 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
45 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
46 | String verifyProfileId = null;
47 | CloseableHttpClient httpClient = null;
48 | try {
49 | httpClient = HttpClients.createDefault();
50 | URI uri = new URIBuilder()
51 | .setScheme("https")
52 | .setHost(tenantHostname)
53 | .setPath("/v1.0/authenticators/clients")
54 | .build();
55 | HttpGet getRequest = new HttpGet(uri);
56 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
57 | getRequest.addHeader("Accept", "application/json");
58 | CloseableHttpResponse response = httpClient.execute(getRequest);
59 | int statusCode = response.getStatusLine().getStatusCode();
60 | String responseBody = EntityUtils.toString(response.getEntity());
61 | EntityUtils.consume(response.getEntity());
62 | if (statusCode == 200) {
63 | Pattern idExtraction = Pattern.compile("\"id\":\"([a-fA-F0-9\\-]+)\"");
64 | Matcher matcher = idExtraction.matcher(responseBody);
65 | if (matcher.find()) {
66 | verifyProfileId = matcher.group(1);
67 | }
68 | } else {
69 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
70 | }
71 | response.close();
72 | } catch (URISyntaxException e) {
73 | e.printStackTrace();
74 | } catch (ClientProtocolException e) {
75 | e.printStackTrace();
76 | } catch (IOException e) {
77 | e.printStackTrace();
78 | } finally {
79 | if (httpClient != null) {
80 | try {
81 | httpClient.close();
82 | } catch (IOException e) {
83 | e.printStackTrace();
84 | }
85 | }
86 | }
87 |
88 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, verifyProfileId);
89 | return verifyProfileId;
90 | }
91 |
92 | public static String initiateVerifyAuthenticatorRegistration(AuthenticationFlowContext context, String userId, String friendlyName) {
93 | final String methodName = "initiateVerifyAuthenticatorRegistration";
94 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, userId, friendlyName);
95 |
96 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
97 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
98 | String verifyProfileId = getVerifyProfileId(context);
99 | CloseableHttpClient httpClient = null;
100 | String qrCode = null;
101 | try {
102 | httpClient = HttpClients.createDefault();
103 | URI uri = new URIBuilder()
104 | .setScheme("https")
105 | .setHost(tenantHostname)
106 | .setPath("/v1.0/authenticators/initiation")
107 | .setParameter("qrcodeInResponse", "true")
108 | .build();
109 | HttpPost postRequest = new HttpPost(uri);
110 | postRequest.addHeader("Authorization", "Bearer " + accessToken);
111 | postRequest.addHeader("Accept", "application/json");
112 | postRequest.addHeader("Content-type", "application/json");
113 | postRequest.setEntity(new StringEntity("{\"clientId\": \"" + verifyProfileId + "\", \"owner\": \"" + userId + "\", \"accountName\": \"" + friendlyName + "\"}"));
114 | CloseableHttpResponse response = httpClient.execute(postRequest);
115 | int statusCode = response.getStatusLine().getStatusCode();
116 | String responseBody = EntityUtils.toString(response.getEntity());
117 | EntityUtils.consume(response.getEntity());
118 | if (statusCode == 200) {
119 | Pattern qrExtraction = Pattern.compile("\"qrcode\":\\s*\"([^\"]+)\"");
120 | Matcher matcher = qrExtraction.matcher(responseBody);
121 | if (matcher.find()) {
122 | qrCode = matcher.group(1);
123 | }
124 | } else {
125 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
126 | }
127 | response.close();
128 | } catch (URISyntaxException e) {
129 | e.printStackTrace();
130 | } catch (ClientProtocolException e) {
131 | e.printStackTrace();
132 | } catch (IOException e) {
133 | e.printStackTrace();
134 | } finally {
135 | if (httpClient != null) {
136 | try {
137 | httpClient.close();
138 | } catch (IOException e) {
139 | e.printStackTrace();
140 | }
141 | }
142 | }
143 |
144 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, qrCode);
145 | return qrCode;
146 | }
147 |
148 | public static boolean doesUserHaveVerifyRegistered(AuthenticationFlowContext context, String userId) {
149 | final String methodName = "doesUserHaveVerifyRegistered";
150 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, userId);
151 |
152 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
153 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
154 | boolean result= false;
155 | CloseableHttpClient httpClient = null;
156 | try {
157 | httpClient = HttpClients.createDefault();
158 | URI uri = new URIBuilder()
159 | .setScheme("https")
160 | .setHost(tenantHostname)
161 | .setPath("/v1.0/authenticators")
162 | .setParameter("search", "owner=\"" + userId + "\"")
163 | .build();
164 | HttpGet getRequest = new HttpGet(uri);
165 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
166 | getRequest.addHeader("Accept", "application/json");
167 | CloseableHttpResponse response = httpClient.execute(getRequest);
168 | int statusCode = response.getStatusLine().getStatusCode();
169 | String responseBody = EntityUtils.toString(response.getEntity());
170 | EntityUtils.consume(response.getEntity());
171 | if (statusCode == 200) {
172 | Pattern idExtraction = Pattern.compile("\"id\":\"[a-fA-F0-9\\-]+\"");
173 | Matcher matcher = idExtraction.matcher(responseBody);
174 | if (matcher.find()) {
175 | result = true;
176 | }
177 | } else {
178 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
179 | }
180 | response.close();
181 | } catch (URISyntaxException e) {
182 | e.printStackTrace();
183 | } catch (ClientProtocolException e) {
184 | e.printStackTrace();
185 | } catch (IOException e) {
186 | e.printStackTrace();
187 | } finally {
188 | if (httpClient != null) {
189 | try {
190 | httpClient.close();
191 | } catch (IOException e) {
192 | e.printStackTrace();
193 | }
194 | }
195 | }
196 |
197 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
198 | return result;
199 | }
200 |
201 | public static String getVerifyRegistrationQrCode(AuthenticationFlowContext context) {
202 | final String methodName = "getVerifyRegistrationQrCode";
203 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
204 |
205 | String result = context.getAuthenticationSession().getUserSessionNotes().get("verify.registration.qr");
206 |
207 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
208 | return result;
209 | }
210 |
211 | public static void setVerifyRegistrationQrCode(AuthenticationFlowContext context, String qrCode) {
212 | final String methodName = "setVerifyRegistrationQrCode";
213 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, qrCode);
214 |
215 | context.getAuthenticationSession().setUserSessionNote("verify.registration.qr", qrCode);
216 |
217 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/otp/IBMSecurityVerifyOtpLoginAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.otp;
15 |
16 | import java.util.List;
17 |
18 | import javax.ws.rs.core.MultivaluedMap;
19 | import javax.ws.rs.core.Response;
20 |
21 | import org.jboss.logging.Logger;
22 | import org.keycloak.authentication.AuthenticationFlowContext;
23 | import org.keycloak.authentication.AuthenticationFlowError;
24 | import org.keycloak.authentication.Authenticator;
25 | import org.keycloak.models.KeycloakSession;
26 | import org.keycloak.models.RealmModel;
27 | import org.keycloak.models.UserModel;
28 | import org.keycloak.models.utils.FormMessage;
29 |
30 | import com.ibm.security.verify.authenticator.rest.FormUtilities;
31 | import com.ibm.security.verify.authenticator.rest.OtpUtilities;
32 | import com.ibm.security.verify.authenticator.rest.OtpUtilities.TransientOtpResponse;
33 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
34 |
35 | public class IBMSecurityVerifyOtpLoginAuthenticator implements Authenticator {
36 |
37 | /**
38 | * Constants used for the OTP submission template
39 | */
40 | private static final String OTP_SELECTION_TEMPLATE = "otp-selection.ftl";
41 | private static final String OTP_SUBMISSION_TEMPLATE = "otp-validation.ftl";
42 |
43 | private static final String OTP_FORM_NAME_ATTR_NAME = "otpFormName";
44 | private static final String OTP_FORM_NAME_VALUE = "otp";
45 |
46 | private static final String OTP_CORRELATION_FORM_NAME_ATTR_NAME ="otpCorrelationFormName";
47 | private static final String OTP_CORRELATION_FORM_NAME_VALUE = "otpCorrelation";
48 |
49 | private static final String OTP_CORRELATION_FORM_VALUE_ATTR_NAME = "otpCorrelationValue";
50 |
51 | private static final String OTP_TYPE_LABEL_ATTR_NAME = "otpTypeLabel";
52 | private static final String OTP_EMAIL_HINT = "otpEmailHint";
53 | private static final String OTP_SMS_HINT = "otpSmsHint";
54 | private static final String OTP_EMAIL_SUBMISSION_LABEL = "Enter your email OTP";
55 | private static final String OTP_SMS_SUBMISSION_LABEL = "Enter your SMS OTP";
56 |
57 | private static final String OTP_SMS_ATTR_NAME = "phone.number";
58 |
59 | private static final String OTP_TYPE_EMAIL = "email";
60 | private static final String OTP_TYPE_SMS = "sms";
61 |
62 | private Logger logger = Logger.getLogger(IBMSecurityVerifyOtpLoginAuthenticator.class);
63 |
64 | public void action(AuthenticationFlowContext context) {
65 | final String methodName = "action";
66 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
67 |
68 | String incomingOtpType = OtpUtilities.getOtpType(context);
69 | MultivaluedMap formParams = context.getHttpRequest().getDecodedFormParameters();
70 | if (OTP_TYPE_EMAIL.equals(incomingOtpType) || OTP_TYPE_SMS.equals(incomingOtpType)) {
71 | String correlation = OtpUtilities.getOtpCorrelation(context);
72 | String transactionId = OtpUtilities.getOtpTransactionId(context);
73 | String otpValue = formParams.getFirst(OTP_FORM_NAME_VALUE);
74 |
75 | boolean isOtpValid = false;
76 | String otpSubmissionLabel = null;
77 | if (OTP_TYPE_EMAIL.equals(incomingOtpType)) {
78 | isOtpValid = OtpUtilities.validateEmailOtp(context, transactionId, otpValue);
79 | otpSubmissionLabel = OTP_EMAIL_SUBMISSION_LABEL;
80 | } else {
81 | isOtpValid = OtpUtilities.validateSmsOtp(context, transactionId, otpValue);
82 | otpSubmissionLabel = OTP_SMS_SUBMISSION_LABEL;
83 | }
84 | if (isOtpValid) {
85 | context.success();
86 | } else {
87 | Response challenge = context.form()
88 | .setAttribute(OTP_FORM_NAME_ATTR_NAME, OTP_FORM_NAME_VALUE)
89 | .setAttribute(OTP_CORRELATION_FORM_NAME_ATTR_NAME, OTP_CORRELATION_FORM_NAME_VALUE)
90 | .setAttribute(OTP_CORRELATION_FORM_VALUE_ATTR_NAME, correlation)
91 | .setAttribute(OTP_TYPE_LABEL_ATTR_NAME, otpSubmissionLabel)
92 | .setError("OTP is invalid")
93 | .createForm(OTP_SUBMISSION_TEMPLATE);
94 | context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge);
95 | }
96 | } else {
97 | // OTP has not been sent yet, the choice has just been made
98 | String email = context.getUser().getEmail();
99 | List phoneValues = context.getUser().getAttribute(OTP_SMS_ATTR_NAME);
100 | String otpType = formParams.getFirst("otpType");
101 | if (OTP_TYPE_EMAIL.equals(otpType)) {
102 | String emailHint = " (" + email.substring(0,3) + "..." + email.substring(email.indexOf("@"), email.length()) + ")";
103 | OtpUtilities.setOtpType(context, OTP_TYPE_EMAIL);
104 | TransientOtpResponse otpResponse = OtpUtilities.sendEmailOtp(context, email);
105 | if (otpResponse != null) {
106 | OtpUtilities.setOtpCorrelation(context, otpResponse.correlation);
107 | OtpUtilities.setOtpTransactionId(context, otpResponse.transactionId);
108 | Response challenge = context.form()
109 | .setAttribute(OTP_FORM_NAME_ATTR_NAME, OTP_FORM_NAME_VALUE)
110 | .setAttribute(OTP_CORRELATION_FORM_NAME_ATTR_NAME, OTP_CORRELATION_FORM_NAME_VALUE)
111 | .setAttribute(OTP_CORRELATION_FORM_VALUE_ATTR_NAME, otpResponse.correlation)
112 | .setAttribute(OTP_TYPE_LABEL_ATTR_NAME, OTP_EMAIL_SUBMISSION_LABEL + emailHint)
113 | .createForm(OTP_SUBMISSION_TEMPLATE);
114 | context.challenge(challenge);
115 | }
116 | } else if (OTP_TYPE_SMS.equals(otpType)) {
117 | OtpUtilities.setOtpType(context, OTP_TYPE_SMS);
118 | String phoneNumber = phoneValues.get(0);
119 | String phoneHint = " (..." + phoneNumber.substring(phoneNumber.length() - 4) + ")";
120 | TransientOtpResponse otpResponse = OtpUtilities.sendSmsOtp(context, phoneNumber );
121 | if (otpResponse != null) {
122 | OtpUtilities.setOtpCorrelation(context, otpResponse.correlation);
123 | OtpUtilities.setOtpTransactionId(context, otpResponse.transactionId);
124 | Response challenge = context.form()
125 | .setAttribute(OTP_FORM_NAME_ATTR_NAME, OTP_FORM_NAME_VALUE)
126 | .setAttribute(OTP_CORRELATION_FORM_NAME_ATTR_NAME, OTP_CORRELATION_FORM_NAME_VALUE)
127 | .setAttribute(OTP_CORRELATION_FORM_VALUE_ATTR_NAME, otpResponse.correlation)
128 | .setAttribute(OTP_TYPE_LABEL_ATTR_NAME, OTP_SMS_SUBMISSION_LABEL + phoneHint)
129 | .createForm(OTP_SUBMISSION_TEMPLATE);
130 | context.challenge(challenge);
131 | }
132 | }
133 | }
134 |
135 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
136 | }
137 |
138 | public void authenticate(AuthenticationFlowContext context) {
139 | final String methodName = "authenticate";
140 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
141 |
142 | if (context.getUser() == null) {
143 | context.challenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsg2FARequired")));
144 | return;
145 | }
146 |
147 | String email = context.getUser().getEmail();
148 | List phoneValues = context.getUser().getAttribute(OTP_SMS_ATTR_NAME);
149 |
150 | if (email != null && !phoneValues.isEmpty()) {
151 | // Allow user to select OTP method
152 | String emailHint = "(" + email.substring(0,3) + "..." + email.substring(email.indexOf("@"), email.length()) + ")";
153 | String phoneHint = "(..." + phoneValues.get(0).substring(phoneValues.get(0).length() - 4) + ")";
154 | Response challenge = context.form()
155 | .setAttribute(OTP_EMAIL_HINT, emailHint)
156 | .setAttribute(OTP_SMS_HINT, phoneHint)
157 | .createForm(OTP_SELECTION_TEMPLATE);
158 | context.challenge(challenge);
159 | } else if (email != null) {
160 | String emailHint = " (" + email.substring(0,3) + "..." + email.substring(email.indexOf("@"), email.length()) + ")";
161 | OtpUtilities.setOtpType(context, OTP_TYPE_EMAIL);
162 | TransientOtpResponse otpResponse = OtpUtilities.sendEmailOtp(context, email);
163 | if (otpResponse != null) {
164 | OtpUtilities.setOtpCorrelation(context, otpResponse.correlation);
165 | OtpUtilities.setOtpTransactionId(context, otpResponse.transactionId);
166 | Response challenge = context.form()
167 | .setAttribute(OTP_FORM_NAME_ATTR_NAME, OTP_FORM_NAME_VALUE)
168 | .setAttribute(OTP_CORRELATION_FORM_NAME_ATTR_NAME, OTP_CORRELATION_FORM_NAME_VALUE)
169 | .setAttribute(OTP_CORRELATION_FORM_VALUE_ATTR_NAME, otpResponse.correlation)
170 | .setAttribute(OTP_TYPE_LABEL_ATTR_NAME, OTP_EMAIL_SUBMISSION_LABEL + emailHint)
171 | .createForm(OTP_SUBMISSION_TEMPLATE);
172 | context.challenge(challenge);
173 | }
174 | } else if (!phoneValues.isEmpty()) {
175 | OtpUtilities.setOtpType(context, OTP_TYPE_SMS);
176 | String phoneNumber = phoneValues.get(0);
177 | String phoneHint = " (..." + phoneNumber.substring(phoneNumber.length() - 4) + ")";
178 | TransientOtpResponse otpResponse = OtpUtilities.sendSmsOtp(context, phoneNumber );
179 | if (otpResponse != null) {
180 | OtpUtilities.setOtpCorrelation(context, otpResponse.correlation);
181 | OtpUtilities.setOtpTransactionId(context, otpResponse.transactionId);
182 | Response challenge = context.form()
183 | .setAttribute(OTP_FORM_NAME_ATTR_NAME, OTP_FORM_NAME_VALUE)
184 | .setAttribute(OTP_CORRELATION_FORM_NAME_ATTR_NAME, OTP_CORRELATION_FORM_NAME_VALUE)
185 | .setAttribute(OTP_CORRELATION_FORM_VALUE_ATTR_NAME, otpResponse.correlation)
186 | .setAttribute(OTP_TYPE_LABEL_ATTR_NAME, OTP_SMS_SUBMISSION_LABEL + phoneHint)
187 | .createForm(OTP_SUBMISSION_TEMPLATE);
188 | context.challenge(challenge);
189 | }
190 | } else {
191 | context.forceChallenge(FormUtilities.createErrorPage(context, new FormMessage("errorMsgMissingEmailAndPhoneNumber")));
192 | return;
193 | }
194 |
195 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
196 | }
197 |
198 | public void close() {
199 | final String methodName = "close";
200 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
201 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
202 | }
203 |
204 |
205 | public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
206 | final String methodName = "configuredFor";
207 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
208 |
209 | boolean result = true;
210 |
211 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
212 | return result;
213 | }
214 |
215 | public boolean requiresUser() {
216 | final String methodName = "requiresUser";
217 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
218 |
219 | // Return true because this authenticator is used for a 2FA scenario
220 | boolean result = true;
221 |
222 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
223 | return result;
224 | }
225 |
226 | public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
227 | final String methodName = "setRequiredActions";
228 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, session, realm, user);
229 | // no-op if we don't want to add any required actions to the login flow
230 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/rest/PushNotificationUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.rest;
15 |
16 | import java.io.IOException;
17 | import java.net.InetAddress;
18 | import java.net.URI;
19 | import java.net.URISyntaxException;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | import org.apache.http.client.ClientProtocolException;
24 | import org.apache.http.client.methods.CloseableHttpResponse;
25 | import org.apache.http.client.methods.HttpGet;
26 | import org.apache.http.client.methods.HttpPost;
27 | import org.apache.http.client.utils.URIBuilder;
28 | import org.apache.http.entity.StringEntity;
29 | import org.apache.http.impl.client.CloseableHttpClient;
30 | import org.apache.http.impl.client.HttpClients;
31 | import org.apache.http.util.EntityUtils;
32 | import org.jboss.logging.Logger;
33 | import org.keycloak.authentication.AuthenticationFlowContext;
34 |
35 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
36 |
37 | public class PushNotificationUtilities {
38 |
39 | private static Logger logger = Logger.getLogger(PushNotificationUtilities.class);
40 | final public static String PUSH_NOTIFICATION_AUTHENTICATOR_ID_ATTR_NAME = "push.notification.authenticator.id";
41 | final public static String PUSH_NOTIFICATION_TRANSACTION_ID_ATTR_NAME = "push.notification.transaction.id";
42 |
43 |
44 | public static Pair getSignatureEnrollmentAuthenticatorId(AuthenticationFlowContext context, String ciUserId) {
45 | final String methodName = "getSignatureEnrollmentId";
46 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, ciUserId);
47 |
48 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
49 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
50 | CloseableHttpClient httpClient = null;
51 | String sigId = null;
52 | String authId = null;
53 |
54 | try {
55 | httpClient = HttpClients.createDefault();
56 | URI uri = new URIBuilder()
57 | .setScheme("https")
58 | .setHost(tenantHostname)
59 | .setPath("/v1.0/authnmethods/signatures")
60 | .setParameter("search", "owner=\"" + ciUserId + "\"")
61 | .build();
62 | HttpGet getRequest = new HttpGet(uri);
63 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
64 | getRequest.addHeader("Accept", "application/json");
65 | getRequest.addHeader("Content-type", "application/json");
66 | CloseableHttpResponse response = httpClient.execute(getRequest);
67 | int statusCode = response.getStatusLine().getStatusCode();
68 | String responseBody = EntityUtils.toString(response.getEntity());
69 | EntityUtils.consume(response.getEntity());
70 | if (statusCode == 200) {
71 | Pattern sigIdExtraction = Pattern.compile("\"id\":\\s*\"([a-fA-F0-9\\-]+)\"");
72 | Pattern authenticatorIdExtraction = Pattern.compile("\"authenticatorId\":\\s*\"([a-fA-F0-9\\-]+)\"");
73 | Matcher sigMatcher = sigIdExtraction.matcher(responseBody);
74 | Matcher authMatcher = authenticatorIdExtraction.matcher(responseBody);
75 | if (sigMatcher.find()) {
76 | sigId = sigMatcher.group(1);
77 | }
78 | if (authMatcher.find()) {
79 | authId = authMatcher.group(1);
80 | }
81 | } else {
82 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: $s", statusCode, responseBody));
83 | }
84 | response.close();
85 | } catch (URISyntaxException e) {
86 | e.printStackTrace();
87 | } catch (ClientProtocolException e) {
88 | e.printStackTrace();
89 | } catch (IOException e) {
90 | e.printStackTrace();
91 | } finally {
92 | if (httpClient != null) {
93 | try {
94 | httpClient.close();
95 | } catch (IOException e) {
96 | e.printStackTrace();
97 | }
98 | }
99 | }
100 |
101 | Pair result = new Pair(sigId, authId);
102 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, (result != null) ? result.toString() : "null");
103 | return result;
104 | }
105 |
106 | public static String sendPushNotification(AuthenticationFlowContext context, String sigId, String authenticatorId) {
107 | final String methodName = "sendPushNotification";
108 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, authenticatorId, sigId);
109 |
110 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
111 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
112 | CloseableHttpClient httpClient = null;
113 | String transactionId = null;
114 | try {
115 | httpClient = HttpClients.createDefault();
116 | URI uri = new URIBuilder()
117 | .setScheme("https")
118 | .setHost(tenantHostname)
119 | .setPath("/v1.0/authenticators/"+ authenticatorId + "/verifications")
120 | .build();
121 | HttpPost postRequest = new HttpPost(uri);
122 | postRequest.addHeader("Authorization", "Bearer " + accessToken);
123 | postRequest.addHeader("Accept", "application/json");
124 | postRequest.addHeader("Content-type", "application/json");
125 |
126 | String originIpAddress = InetAddress.getLocalHost().getHostAddress();
127 | // TODO: Get user browser information
128 | String originUserAgent = "Mozilla Firefox 11";
129 |
130 | postRequest.setEntity(
131 | new StringEntity(PushNotificationPostRequestBody.generate(sigId, originIpAddress, originUserAgent)));
132 | CloseableHttpResponse response = httpClient.execute(postRequest);
133 | int statusCode = response.getStatusLine().getStatusCode();
134 | String responseBody = EntityUtils.toString(response.getEntity());
135 | EntityUtils.consume(response.getEntity());
136 | if (statusCode == 202) {
137 | Pattern transactionIdExtraction = Pattern.compile("\"authenticatorId\":.*\"id\":\\s*\"([a-fA-F0-9\\-]+)\"");
138 | Matcher matcher = transactionIdExtraction.matcher(responseBody);
139 |
140 | if (matcher.find()) {
141 | transactionId = matcher.group(1);
142 | context.getAuthenticationSession().setAuthNote(PUSH_NOTIFICATION_AUTHENTICATOR_ID_ATTR_NAME, authenticatorId);
143 | context.getAuthenticationSession().setAuthNote(PUSH_NOTIFICATION_TRANSACTION_ID_ATTR_NAME, transactionId);
144 | }
145 | } else {
146 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
147 | }
148 | response.close();
149 | } catch (URISyntaxException e) {
150 | e.printStackTrace();
151 | } catch (ClientProtocolException e) {
152 | e.printStackTrace();
153 | } catch (IOException e) {
154 | e.printStackTrace();
155 | } finally {
156 | if (httpClient != null) {
157 | try {
158 | httpClient.close();
159 | } catch (IOException e) {
160 | e.printStackTrace();
161 | }
162 | }
163 | }
164 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, transactionId);
165 | return transactionId;
166 | }
167 |
168 | public static String getPushNotificationVerification(AuthenticationFlowContext context) {
169 | final String methodName = "getPushNotificationVerification";
170 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName);
171 |
172 | String authenticatorId = context.getAuthenticationSession().getAuthNote(PUSH_NOTIFICATION_AUTHENTICATOR_ID_ATTR_NAME);
173 | String transactionId = context.getAuthenticationSession().getAuthNote(PUSH_NOTIFICATION_TRANSACTION_ID_ATTR_NAME);
174 | if (authenticatorId == null || transactionId == null) {
175 | // TODO: Error!
176 | }
177 |
178 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
179 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
180 | CloseableHttpClient httpClient = null;
181 | String pushNotificationState = null;
182 |
183 | try {
184 | httpClient = HttpClients.createDefault();
185 | URI uri = new URIBuilder()
186 | .setScheme("https")
187 | .setHost(tenantHostname)
188 | .setPath("/v1.0/authenticators/" + authenticatorId + "/verifications")
189 | .setParameter("search", "id=\"" + transactionId + "\"")
190 | .build();
191 | HttpGet getRequest = new HttpGet(uri);
192 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
193 | getRequest.addHeader("Accept", "application/json");
194 | getRequest.addHeader("Content-type", "application/json");
195 | CloseableHttpResponse response = httpClient.execute(getRequest);
196 | int statusCode = response.getStatusLine().getStatusCode();
197 | String responseBody = EntityUtils.toString(response.getEntity());
198 | EntityUtils.consume(response.getEntity());
199 | if (statusCode == 200) {
200 | Pattern idExtraction = Pattern.compile("\"state\":\\s*\"([a-zA-Z\\_]+)\"");
201 | Matcher matcher = idExtraction.matcher(responseBody);
202 | if (matcher.find()) {
203 | pushNotificationState = matcher.group(1);
204 | }
205 | } else {
206 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
207 | }
208 | response.close();
209 | } catch (URISyntaxException e) {
210 | e.printStackTrace();
211 | } catch (ClientProtocolException e) {
212 | e.printStackTrace();
213 | } catch (IOException e) {
214 | e.printStackTrace();
215 | } finally {
216 | if (httpClient != null) {
217 | try {
218 | httpClient.close();
219 | } catch (IOException e) {
220 | e.printStackTrace();
221 | }
222 | }
223 | }
224 |
225 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, pushNotificationState);
226 | return pushNotificationState;
227 | }
228 |
229 | public static class PushNotificationPostRequestBody {
230 | public static String generate(String sigId, String originIpAddress, String originUserAgent) {
231 | return String.format(
232 | "{\"expiresIn\": 120, \"pushNotification\": " +
233 | "{\"sound\": \"default\", \"message\": \"You have a pending authentication challenge\", " +
234 | "\"useDevCreds\": true, \"send\": true, \"title\": \"IBM Verify\"}, " +
235 | "\"authenticationMethods\": [{\"methodType\": \"signature\", " +
236 | "\"id\": \"%s\"}], \"logic\": \"OR\", " +
237 | "\"transactionData\": {\"message\": \"You have a pending authentication challenge\", " +
238 | "\"originIpAddress\": \"%s\", \"originUserAgent\": \"%s\"}}", sigId, originIpAddress, originUserAgent);
239 | }
240 | }
241 |
242 | public static class Pair {
243 | public final K key;
244 | public final V value;
245 |
246 | public Pair(K key, V value) {
247 | this.key = key;
248 | this.value = value;
249 | }
250 |
251 | public String toString() {
252 | return key.toString() + ":" + value.toString();
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/IBMSecurityVerifyAuthenticators/src/main/java/com/ibm/security/verify/authenticator/rest/IBMSecurityVerifyUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 IBM
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 |
14 | package com.ibm.security.verify.authenticator.rest;
15 |
16 | import java.io.IOException;
17 | import java.net.URI;
18 | import java.net.URISyntaxException;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.regex.Matcher;
23 | import java.util.regex.Pattern;
24 |
25 | import javax.ws.rs.core.Response;
26 |
27 | import org.apache.http.NameValuePair;
28 | import org.apache.http.client.ClientProtocolException;
29 | import org.apache.http.client.entity.UrlEncodedFormEntity;
30 | import org.apache.http.client.methods.CloseableHttpResponse;
31 | import org.apache.http.client.methods.HttpGet;
32 | import org.apache.http.client.methods.HttpPost;
33 | import org.apache.http.client.utils.URIBuilder;
34 | import org.apache.http.entity.StringEntity;
35 | import org.apache.http.impl.client.CloseableHttpClient;
36 | import org.apache.http.impl.client.HttpClients;
37 | import org.apache.http.message.BasicNameValuePair;
38 | import org.apache.http.util.EntityUtils;
39 | import org.jboss.logging.Logger;
40 | import org.keycloak.authentication.AuthenticationFlowContext;
41 | import org.keycloak.authentication.AuthenticationFlowError;
42 | import org.keycloak.models.AuthenticatorConfigModel;
43 | import org.keycloak.models.UserModel;
44 |
45 | import com.ibm.security.verify.authenticator.utils.IBMSecurityVerifyLoggingUtilities;
46 |
47 | public class IBMSecurityVerifyUtilities {
48 |
49 | public static final String CONFIG_TENANT_FQDN = "tenant.fqdn";
50 | public static final String CONFIG_CLIENT_ID = "client.id";
51 | public static final String CONFIG_CLIENT_SECRET = "client.secret";
52 |
53 | private static Logger logger = Logger.getLogger(IBMSecurityVerifyUtilities.class);
54 |
55 | public static String getAccessToken(AuthenticationFlowContext context) {
56 | final String methodName = "getAccessToken";
57 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
58 | String accessToken = null;
59 |
60 | String tenantHostname = getTenantHostname(context);
61 | CloseableHttpClient httpClient = null;
62 | try {
63 | AuthenticatorConfigModel authenticatorConfigModel = context.getAuthenticatorConfig();
64 | if (authenticatorConfigModel != null) {
65 | Map authenticatorConfig = authenticatorConfigModel.getConfig();
66 | if (authenticatorConfig != null) {
67 | // Load tenant configuration
68 | String clientId = authenticatorConfig.get(IBMSecurityVerifyUtilities.CONFIG_CLIENT_ID);
69 | String clientSecret = authenticatorConfig.get(IBMSecurityVerifyUtilities.CONFIG_CLIENT_SECRET);
70 |
71 | // Request for the access token
72 | httpClient = HttpClients.createDefault();
73 | URI uri = new URIBuilder()
74 | .setScheme("https")
75 | .setHost(tenantHostname)
76 | .setPath("/v1.0/endpoint/default/token")
77 | .build();
78 | HttpPost post = new HttpPost(uri);
79 | List params = new ArrayList();
80 | params.add(new BasicNameValuePair("client_id", clientId));
81 | params.add(new BasicNameValuePair("client_secret", clientSecret));
82 | params.add(new BasicNameValuePair("grant_type", "client_credentials"));
83 | post.setEntity(new UrlEncodedFormEntity(params));
84 | CloseableHttpResponse response = httpClient.execute(post);
85 | int statusCode = response.getStatusLine().getStatusCode();
86 | String responseBody = EntityUtils.toString(response.getEntity());
87 | EntityUtils.consume(response.getEntity());
88 | if (statusCode == 200) {
89 | Pattern accessTokenExtraction = Pattern.compile("\"access_token\":\"([a-zA-Z0-9]+)\"");
90 | Matcher matcher = accessTokenExtraction.matcher(responseBody);
91 | if (matcher.find()) {
92 | accessToken = matcher.group(1);
93 | }
94 | } else {
95 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
96 | }
97 | response.close();
98 | }
99 | }
100 | } catch (URISyntaxException e) {
101 | e.printStackTrace();
102 | } catch (ClientProtocolException e) {
103 | e.printStackTrace();
104 | } catch (IOException e) {
105 | e.printStackTrace();
106 | } finally {
107 | if (httpClient != null) {
108 | try {
109 | httpClient.close();
110 | } catch (IOException e) {
111 | e.printStackTrace();
112 | }
113 | }
114 | }
115 |
116 | if (accessToken == null) {
117 | // TODO: immediately abort the flow
118 | Response challengeResponse = Response.serverError().build();
119 | context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
120 | }
121 |
122 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, accessToken);
123 | return accessToken;
124 | }
125 |
126 | public static String getTenantHostname(AuthenticationFlowContext context) {
127 | final String methodName = "getTenantHostname";
128 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
129 |
130 | String tenantHostname = null;
131 | AuthenticatorConfigModel authenticatorConfigModel = context.getAuthenticatorConfig();
132 | if (authenticatorConfigModel != null) {
133 | Map authenticatorConfig = authenticatorConfigModel.getConfig();
134 | if (authenticatorConfig != null) {
135 | // Load tenant configuration
136 | tenantHostname = authenticatorConfig.get(IBMSecurityVerifyUtilities.CONFIG_TENANT_FQDN);
137 | }
138 | }
139 |
140 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, tenantHostname);
141 | return tenantHostname;
142 | }
143 |
144 | public static UserModel matchCIUserIdToUserModel(AuthenticationFlowContext context, String userId) {
145 | final String methodName = "matchCIUserIdToUserModel";
146 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, userId);
147 |
148 | String kcUserId = getKCUserId(context, userId);
149 | UserModel matchingUser = null;
150 |
151 | if (kcUserId != null) {
152 | List users = context.getSession().users().getUsers(context.getRealm());
153 | UserModel iterUser;
154 | String id;
155 | for (int i = 0; i < users.size(); i++) {
156 | iterUser = users.get(i);
157 | id = iterUser.getId();
158 | if (kcUserId.equals(id)) {
159 | matchingUser = iterUser;
160 | i = users.size();
161 | }
162 | }
163 | } else {
164 | // TODO: Error - mismatch / user does not exist
165 | }
166 |
167 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, matchingUser != null ? matchingUser.toString() : null);
168 | return matchingUser;
169 | }
170 |
171 | public static String getCIUserId(AuthenticationFlowContext context, UserModel user) {
172 | final String methodName = "getCIUserId";
173 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, user);
174 |
175 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
176 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
177 | String ciUserId = null;
178 | CloseableHttpClient httpClient = null;
179 | try {
180 | httpClient = HttpClients.createDefault();
181 | URI uri = new URIBuilder()
182 | .setScheme("https")
183 | .setHost(tenantHostname)
184 | .setPath("/v2.0/Users")
185 | .setParameter("fullText", user.getId())
186 | .build();
187 | HttpGet getRequest = new HttpGet(uri);
188 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
189 | getRequest.addHeader("Accept", "application/scim+json");
190 | CloseableHttpResponse response = httpClient.execute(getRequest);
191 | int statusCode = response.getStatusLine().getStatusCode();
192 | String responseBody = EntityUtils.toString(response.getEntity());
193 | EntityUtils.consume(response.getEntity());
194 | if (statusCode == 200) {
195 | Pattern idExtraction = Pattern.compile("\"id\":\"([A-Z0-9]+)\"");
196 | Matcher matcher = idExtraction.matcher(responseBody);
197 | if (matcher.find()) {
198 | ciUserId = matcher.group(1);
199 | }
200 | } else {
201 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
202 | }
203 | response.close();
204 | } catch (URISyntaxException e) {
205 | e.printStackTrace();
206 | } catch (ClientProtocolException e) {
207 | e.printStackTrace();
208 | } catch (IOException e) {
209 | e.printStackTrace();
210 | } finally {
211 | if (httpClient != null) {
212 | try {
213 | httpClient.close();
214 | } catch (IOException e) {
215 | e.printStackTrace();
216 | }
217 | }
218 | }
219 |
220 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, ciUserId);
221 | return ciUserId;
222 | }
223 |
224 | public static String getKCUserId(AuthenticationFlowContext context, String ciUserId) {
225 | final String methodName = "getKCUserId";
226 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, ciUserId);
227 |
228 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
229 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
230 | String kcUserId = null;
231 | CloseableHttpClient httpClient = null;
232 | try {
233 | httpClient = HttpClients.createDefault();
234 | URI uri = new URIBuilder()
235 | .setScheme("https")
236 | .setHost(tenantHostname)
237 | .setPath("/v2.0/Users")
238 | .setParameter("fullText", ciUserId)
239 | .build();
240 | HttpGet getRequest = new HttpGet(uri);
241 | getRequest.addHeader("Authorization", "Bearer " + accessToken);
242 | getRequest.addHeader("Accept", "application/scim+json");
243 | CloseableHttpResponse response = httpClient.execute(getRequest);
244 | int statusCode = response.getStatusLine().getStatusCode();
245 | String responseBody = EntityUtils.toString(response.getEntity());
246 | EntityUtils.consume(response.getEntity());
247 | if (statusCode == 200) {
248 | Pattern idExtraction = Pattern.compile("\"externalId\":\"([a-f0-9\\-]+)\"");
249 | Matcher matcher = idExtraction.matcher(responseBody);
250 | if (matcher.find()) {
251 | kcUserId = matcher.group(1);
252 | }
253 | } else {
254 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
255 | }
256 | response.close();
257 | } catch (URISyntaxException e) {
258 | e.printStackTrace();
259 | } catch (ClientProtocolException e) {
260 | e.printStackTrace();
261 | } catch (IOException e) {
262 | e.printStackTrace();
263 | } finally {
264 | if (httpClient != null) {
265 | try {
266 | httpClient.close();
267 | } catch (IOException e) {
268 | e.printStackTrace();
269 | }
270 | }
271 | }
272 |
273 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, kcUserId);
274 | return kcUserId;
275 | }
276 |
277 | public static boolean createCIShadowUser(AuthenticationFlowContext context, UserModel user) {
278 | final String methodName = "createCIShadowUser";
279 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context, user);
280 |
281 | boolean result = false;
282 | String tenantHostname = IBMSecurityVerifyUtilities.getTenantHostname(context);
283 | String accessToken = IBMSecurityVerifyUtilities.getAccessToken(context);
284 | CloseableHttpClient httpClient = null;
285 | try {
286 | httpClient = HttpClients.createDefault();
287 | URI uri = new URIBuilder()
288 | .setScheme("https")
289 | .setHost(tenantHostname)
290 | .setPath("/v2.0/Users")
291 | .build();
292 | HttpPost postRequest = new HttpPost(uri);
293 | postRequest.addHeader("Authorization", "Bearer " + accessToken);
294 | postRequest.addHeader("Accept", "application/scim+json");
295 | postRequest.addHeader("Content-type", "application/scim+json");
296 | String createUserPayload = String.format(
297 | "{\"userName\": \"%s\",\"urn:ietf:params:scim:schemas:extension:ibm:2.0:Notification\": {\"notifyType\": \"NONE\"}, \"externalId\": \"%s\", \"emails\": [{\"type\": \"work\", \"value\": \"%s\"}], \"schemas\": [\"urn:ietf:params:scim:schemas:core:2.0:User\", \"urn:ietf:params:scim:schemas:extension:ibm:2.0:Notification\"]}",
298 | user.getUsername(),
299 | user.getId(),
300 | user.getEmail()
301 | );
302 | postRequest.setEntity(new StringEntity(createUserPayload));
303 | CloseableHttpResponse response = httpClient.execute(postRequest);
304 | int statusCode = response.getStatusLine().getStatusCode();
305 | String responseBody = EntityUtils.toString(response.getEntity());
306 | EntityUtils.consume(response.getEntity());
307 | if (statusCode == 201) {
308 | Pattern idExtraction = Pattern.compile("\"id\":\\s*\"(\\w+)\"");
309 | Matcher matcher = idExtraction.matcher(responseBody);
310 | if (matcher.find()) {
311 | String ciUserId = matcher.group(1);
312 | if (ciUserId != null) {
313 | //setCIUserId(user, ciUserId);
314 | result = true;
315 | }
316 | }
317 | } else {
318 | IBMSecurityVerifyLoggingUtilities.error(logger, methodName, String.format("%s: %s", statusCode, responseBody));
319 | }
320 | response.close();
321 | } catch (URISyntaxException e) {
322 | e.printStackTrace();
323 | } catch (ClientProtocolException e) {
324 | e.printStackTrace();
325 | } catch (IOException e) {
326 | e.printStackTrace();
327 | } finally {
328 | if (httpClient != null) {
329 | try {
330 | httpClient.close();
331 | } catch (IOException e) {
332 | e.printStackTrace();
333 | }
334 | }
335 | }
336 |
337 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, result);
338 | return result;
339 | }
340 |
341 | public static void setPromptedPasswordlessRegistration(AuthenticationFlowContext context) {
342 | final String methodName = "setPromptedPasswordlessRegistration";
343 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
344 |
345 | context.getAuthenticationSession().setAuthNote("prompt.passwordless.registration", Boolean.TRUE.toString());
346 |
347 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
348 | }
349 |
350 | public static void clearPromptedPasswordlessRegistration(AuthenticationFlowContext context) {
351 | final String methodName = "clearPromptedPasswordlessRegistration";
352 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
353 |
354 | context.getAuthenticationSession().setAuthNote("prompt.passwordless.registration", Boolean.FALSE.toString());
355 |
356 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName);
357 | }
358 |
359 | public static boolean hasPromptedPasswordlessRegistration(AuthenticationFlowContext context) {
360 | final String methodName = "hasPromptedPasswordlessRegistration";
361 | IBMSecurityVerifyLoggingUtilities.entry(logger, methodName, context);
362 |
363 | String authNote = context.getAuthenticationSession().getAuthNote("prompt.passwordless.registration");
364 | boolean hasPrompted = authNote == null ? false : Boolean.valueOf(authNote);
365 |
366 | IBMSecurityVerifyLoggingUtilities.exit(logger, methodName, hasPrompted);
367 | return hasPrompted;
368 | }
369 | }
370 |
--------------------------------------------------------------------------------