/* * Copyright (C) 2014 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.errai.security.keycloak; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.Map; import org.jboss.errai.security.keycloak.mock.MockWrappedAuthenticationService; import org.jboss.errai.security.keycloak.properties.KeycloakPropertyNames; import org.jboss.errai.security.shared.api.identity.User; import org.jboss.errai.security.shared.api.identity.User.StandardUserProperties; import org.jboss.errai.security.shared.api.identity.UserImpl; import org.jboss.errai.security.shared.exception.AlreadyLoggedInException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.keycloak.KeycloakSecurityContext; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken.Access; import org.keycloak.representations.AddressClaimSet; import org.mockito.InjectMocks; import org.mockito.Spy; import org.mockito.runners.MockitoJUnitRunner; /** * @author Max Barkley <mbarkley@redhat.com> */ @RunWith(MockitoJUnitRunner.class) public class KeycloakAuthenticationServiceTest { private static final String PREFERRED_USERNAME = "john-uid"; private static final String USER_ROLE = "user"; private static final String ZONE_INFO = "zone"; private static final String WEBSITE = "http://www.johndoe.com"; private static final String STREET_ADDRESS = "123"; private static final String REGION = "ON"; private static final String PROFILE = "http://www.somesite.com/jd1970"; private static final String POSTAL_CODE = "A1B2C3"; private static final String PICTURE = "http://www.somesite.com/some-picture"; private static final boolean PHONE_NUMBER_VERIFIED = true; private static final String PHONE_NUMBER = "012-345-6789"; private static final String NICK_NAME = "Johnny"; private static final String NAME = "John Doe"; private static final String MIDDLE_NAME = "Anonymous"; private static final String LOCALITY = "some locality"; private static final String LOCALE = "en_US"; private static final String GIVEN_NAME = "John"; private static final String GENDER = "male"; private static final String FORMATTED_ADDRESS = "123 Fake Street, Toronto, Ontario, Canada"; private static final String FAMILY_NAME = "Doe"; private static final boolean EMAIL_VERIFIED = true; private static final String EMAIL = "john@doe.com"; private static final String COUNTRY = "Canada"; private static final String BIRTHDATE = "January 1st, 1970"; private static final String ERRAI_APP = "http://some-errai-app"; private AssertMap keycloakAssertionMap; private AssertMap wrappedAssertionMap; @InjectMocks private KeycloakAuthenticationService authService; @Spy private MockWrappedAuthenticationService mockWrappedService; private KeycloakSecurityContext securityContext; @Before public void setup() { final AccessToken accessToken = new AccessToken(); setUserProperties(accessToken); // Token strings are never used securityContext = new KeycloakSecurityContext("", accessToken, "", accessToken); keycloakAssertionMap = new AssertMap(new Assertion() { @Override public void doAssertion(final Object expected, final Object observed) { assertEquals(expected, observed); } }); wrappedAssertionMap = new AssertMap(new Assertion() { @Override public void doAssertion(Object expected, Object observed) { assertNull(observed); } }); } private void setUserProperties(final AccessToken accessToken) { accessToken.setPreferredUsername(PREFERRED_USERNAME); accessToken.setAddress(new AddressClaimSet()); accessToken.setBirthdate(BIRTHDATE); accessToken.getAddress().setCountry(COUNTRY); accessToken.setEmail(EMAIL); accessToken.setEmailVerified(EMAIL_VERIFIED); accessToken.setFamilyName(FAMILY_NAME); accessToken.getAddress().setFormattedAddress(FORMATTED_ADDRESS); accessToken.setGender(GENDER); accessToken.setGivenName(GIVEN_NAME); accessToken.setLocale(LOCALE); accessToken.getAddress().setLocality(LOCALITY); accessToken.setMiddleName(MIDDLE_NAME); accessToken.setName(NAME); accessToken.setNickName(NICK_NAME); accessToken.setPhoneNumber(PHONE_NUMBER); accessToken.setPhoneNumberVerified(PHONE_NUMBER_VERIFIED); accessToken.setPicture(PICTURE); accessToken.getAddress().setPostalCode(POSTAL_CODE); accessToken.setProfile(PROFILE); accessToken.getAddress().setRegion(REGION); accessToken.getAddress().setStreetAddress(STREET_ADDRESS); accessToken.setWebsite(WEBSITE); accessToken.setZoneinfo(ZONE_INFO); setUserRoles(accessToken); } private void setUserRoles(final AccessToken accessToken) { final Access access = new Access(); access.addRole(USER_ROLE); Map<String, Access> resourceAccess = new HashMap<String, Access>(); resourceAccess.put(ERRAI_APP, access); accessToken.issuedFor(ERRAI_APP); accessToken.setResourceAccess(resourceAccess); } private void verifyUserProperties(final User user, final AssertMap map) { assertEquals(PREFERRED_USERNAME, user.getIdentifier()); map.get(KeycloakPropertyNames.BIRTHDATE).doAssertion(BIRTHDATE, user.getProperty(KeycloakPropertyNames.BIRTHDATE)); map.get(KeycloakPropertyNames.COUNTRY).doAssertion(COUNTRY, user.getProperty(KeycloakPropertyNames.COUNTRY)); map.get(StandardUserProperties.EMAIL).doAssertion(EMAIL, user.getProperty(StandardUserProperties.EMAIL)); map.get(KeycloakPropertyNames.EMAIL_VERIFIED).doAssertion(String.valueOf(EMAIL_VERIFIED), user.getProperty(KeycloakPropertyNames.EMAIL_VERIFIED)); map.get(StandardUserProperties.LAST_NAME).doAssertion(FAMILY_NAME, user.getProperty(StandardUserProperties.LAST_NAME)); map.get(KeycloakPropertyNames.FORMATTED_ADDRESS).doAssertion(FORMATTED_ADDRESS, user.getProperty(KeycloakPropertyNames.FORMATTED_ADDRESS)); map.get(KeycloakPropertyNames.GENDER).doAssertion(GENDER, user.getProperty(KeycloakPropertyNames.GENDER)); map.get(StandardUserProperties.FIRST_NAME).doAssertion(GIVEN_NAME, user.getProperty(StandardUserProperties.FIRST_NAME)); map.get(KeycloakPropertyNames.LOCALE).doAssertion(LOCALE, user.getProperty(KeycloakPropertyNames.LOCALE)); map.get(KeycloakPropertyNames.LOCALITY).doAssertion(LOCALITY, user.getProperty(KeycloakPropertyNames.LOCALITY)); map.get(KeycloakPropertyNames.MIDDLE_NAME).doAssertion(MIDDLE_NAME, user.getProperty(KeycloakPropertyNames.MIDDLE_NAME)); map.get(KeycloakPropertyNames.NAME).doAssertion(NAME, user.getProperty(KeycloakPropertyNames.NAME)); map.get(KeycloakPropertyNames.NICKNAME).doAssertion(NICK_NAME, user.getProperty(KeycloakPropertyNames.NICKNAME)); map.get(KeycloakPropertyNames.PHONENUMBER).doAssertion(PHONE_NUMBER, user.getProperty(KeycloakPropertyNames.PHONENUMBER)); map.get(KeycloakPropertyNames.PHONENUMBER_VERIFIED).doAssertion(String.valueOf(PHONE_NUMBER_VERIFIED), user.getProperty(KeycloakPropertyNames.PHONENUMBER_VERIFIED)); map.get(KeycloakPropertyNames.PICTURE).doAssertion(PICTURE, user.getProperty(KeycloakPropertyNames.PICTURE)); map.get(KeycloakPropertyNames.POSTAL_CODE).doAssertion(POSTAL_CODE, user.getProperty(KeycloakPropertyNames.POSTAL_CODE)); map.get(KeycloakPropertyNames.PREFERRED_USERNAME).doAssertion(PREFERRED_USERNAME, user.getProperty(KeycloakPropertyNames.PREFERRED_USERNAME)); map.get(KeycloakPropertyNames.PROFILE).doAssertion(PROFILE, user.getProperty(KeycloakPropertyNames.PROFILE)); map.get(KeycloakPropertyNames.REGION).doAssertion(REGION, user.getProperty(KeycloakPropertyNames.REGION)); map.get(KeycloakPropertyNames.STREET_ADDRESS).doAssertion(STREET_ADDRESS, user.getProperty(KeycloakPropertyNames.STREET_ADDRESS)); map.get(KeycloakPropertyNames.WEBSITE).doAssertion(WEBSITE, user.getProperty(KeycloakPropertyNames.WEBSITE)); map.get(KeycloakPropertyNames.ZONE_INFO).doAssertion(ZONE_INFO, user.getProperty(KeycloakPropertyNames.ZONE_INFO)); } private void verifyUserRoles(final UserImpl user, final String... roleNames) { assertTrue(user.hasAllRoles(roleNames)); } @Test public void isLoggedInReturnsFalseWhenNoUserLoggedIn() throws Exception { assertFalse(authService.isLoggedIn()); } @Test public void getUserReturnsAnonymousWhenNoUserLoggedIn() throws Exception { assertEquals(User.ANONYMOUS, authService.getUser()); } @Test public void isLoggedInReturnsTrueWhenKeycloakUserLoggedIn() throws Exception { authService.setSecurityContext(securityContext); assertTrue(authService.isLoggedIn()); } @Test public void getUserReturnsUserWhenKeycloakUserLoggedIn() throws Exception { authService.setSecurityContext(securityContext); final UserImpl user = (UserImpl) authService.getUser(); verifyUserProperties(user, keycloakAssertionMap); verifyUserRoles(user, USER_ROLE); } @Test public void isLoggedInReturnsTrueWhenWrappedUserLoggedIn() throws Exception { mockWrappedService.login(PREFERRED_USERNAME, ""); assertTrue(authService.isLoggedIn()); } @Test public void getUserReturnsUserWhenWrappedUserLoggedIn() throws Exception { mockWrappedService.login(PREFERRED_USERNAME, ""); final UserImpl user = (UserImpl) authService.getUser(); verifyUserProperties(user, wrappedAssertionMap); verifyUserRoles(user); } @Test public void isLoggedInReturnsFalseAfterKeycloakUserLogsOut() throws Exception { // setup isLoggedInReturnsTrueWhenKeycloakUserLoggedIn(); authService.logout(); assertFalse(authService.isLoggedIn()); } @Test public void getUserReturnsAnonymousAfterKeycloakUserLogsOut() throws Exception { getUserReturnsUserWhenKeycloakUserLoggedIn(); authService.logout(); assertEquals(User.ANONYMOUS, authService.getUser()); } @Test public void isLoggedInReturnsFalseAfterWrappedUserLogsOut() throws Exception { isLoggedInReturnsTrueWhenWrappedUserLoggedIn(); authService.logout(); assertFalse(authService.isLoggedIn()); } @Test public void getUserReturnsAnonymousAfterWrappedUserLogsOut() throws Exception { getUserReturnsUserWhenWrappedUserLoggedIn(); authService.logout(); assertEquals(User.ANONYMOUS, authService.getUser()); } @Test public void logoutIsIdempotent() throws Exception { authService.logout(); authService.logout(); isLoggedInReturnsFalseWhenNoUserLoggedIn(); } @Test(expected = AlreadyLoggedInException.class) public void loginFailsIfKeycloakUserLoggedIn() throws Exception { getUserReturnsUserWhenKeycloakUserLoggedIn(); authService.login(PREFERRED_USERNAME, ""); } @Test(expected = AlreadyLoggedInException.class) public void settingKeycloakUserFailsIfWrappedUserLoggedIn() throws Exception { getUserReturnsUserWhenWrappedUserLoggedIn(); authService.setSecurityContext(securityContext); } @Test public void createdKeycloakUserDoesNotHaveUnavailableProperties() throws Exception { securityContext.getToken().setLocale(null); keycloakAssertionMap.put(KeycloakPropertyNames.LOCALE, new Assertion() { @Override public void doAssertion(final Object expected, final Object observed) { assertNull(observed); } }); getUserReturnsUserWhenKeycloakUserLoggedIn(); } private static class AssertMap { private final Assertion defaultAssertion; private final Map<Object, Assertion> assertions; AssertMap(final Assertion defaultComparison) { this.defaultAssertion = defaultComparison; assertions = new HashMap<Object, KeycloakAuthenticationServiceTest.Assertion>(); } Assertion get(final Object key) { final Assertion assertion = assertions.get(key); if (assertion == null) { return defaultAssertion; } else { return assertion; } } void put(final Object key, final Assertion newAssertion) { assertions.put(key, newAssertion); } } private interface Assertion { void doAssertion(Object expected, Object observed); } }