commit
2219cd363e
13 changed files with 409 additions and 58 deletions
|
@ -1019,18 +1019,11 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
copy.add(entity);
|
copy.add(entity);
|
||||||
|
|
||||||
}
|
}
|
||||||
Collections.sort(copy, new Comparator<StorageProviderEntity>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(StorageProviderEntity o1, StorageProviderEntity o2) {
|
|
||||||
return o1.getPriority() - o2.getPriority();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
List<StorageProviderModel> result = new LinkedList<>();
|
List<StorageProviderModel> result = new LinkedList<>();
|
||||||
for (StorageProviderEntity entity : copy) {
|
for (StorageProviderEntity entity : copy) {
|
||||||
result.add(toModel(entity));
|
result.add(toModel(entity));
|
||||||
}
|
}
|
||||||
|
Collections.sort(result, StorageProviderModel.comparator);
|
||||||
|
|
||||||
return Collections.unmodifiableList(result);
|
return Collections.unmodifiableList(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1311,20 +1311,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
copy.add(entity);
|
copy.add(entity);
|
||||||
|
|
||||||
}
|
}
|
||||||
Collections.sort(copy, new Comparator<StorageProviderEntity>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(StorageProviderEntity o1, StorageProviderEntity o2) {
|
|
||||||
return o1.getPriority() - o2.getPriority();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
List<StorageProviderModel> result = new LinkedList<>();
|
List<StorageProviderModel> result = new LinkedList<>();
|
||||||
for (StorageProviderEntity entity : copy) {
|
for (StorageProviderEntity entity : copy) {
|
||||||
result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()
|
result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Collections.sort(result, StorageProviderModel.comparator);
|
||||||
return Collections.unmodifiableList(result);
|
return Collections.unmodifiableList(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,24 +26,25 @@ public class SPMetadataDescriptor {
|
||||||
String descriptor =
|
String descriptor =
|
||||||
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
||||||
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
|
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
|
||||||
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
|
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n";
|
||||||
|
if (wantAuthnRequestsSigned) {
|
||||||
|
descriptor +=
|
||||||
|
" <KeyDescriptor use=\"signing\">\n" +
|
||||||
|
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
|
||||||
|
" <dsig:X509Data>\n" +
|
||||||
|
" <dsig:X509Certificate>\n" + certificatePem + "\n" +
|
||||||
|
" </dsig:X509Certificate>\n" +
|
||||||
|
" </dsig:X509Data>\n" +
|
||||||
|
" </dsig:KeyInfo>\n" +
|
||||||
|
" </KeyDescriptor>\n";
|
||||||
|
}
|
||||||
|
descriptor +=
|
||||||
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
|
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
|
||||||
" <NameIDFormat>" + nameIDPolicyFormat + "\n" +
|
" <NameIDFormat>" + nameIDPolicyFormat + "\n" +
|
||||||
" </NameIDFormat>\n" +
|
" </NameIDFormat>\n" +
|
||||||
" <AssertionConsumerService\n" +
|
" <AssertionConsumerService\n" +
|
||||||
" Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
|
" Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
|
||||||
" index=\"1\" isDefault=\"true\" />\n";
|
" index=\"1\" isDefault=\"true\" />\n";
|
||||||
if (wantAuthnRequestsSigned) {
|
|
||||||
descriptor +=
|
|
||||||
" <KeyDescriptor use=\"signing\">\n" +
|
|
||||||
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
|
|
||||||
" <dsig:X509Data>\n" +
|
|
||||||
" <dsig:X509Certificate>\n" + certificatePem + "\n" +
|
|
||||||
" </dsig:X509Certificate>\n" +
|
|
||||||
" </dsig:X509Data>\n" +
|
|
||||||
" </dsig:KeyInfo>\n" +
|
|
||||||
" </KeyDescriptor>\n";
|
|
||||||
}
|
|
||||||
descriptor +=
|
descriptor +=
|
||||||
" </SPSSODescriptor>\n" +
|
" </SPSSODescriptor>\n" +
|
||||||
"</EntityDescriptor>\n";
|
"</EntityDescriptor>\n";
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.keycloak.storage.StorageProviderModel;
|
||||||
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
import org.keycloak.storage.user.UserLookupProvider;
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
import org.keycloak.storage.user.UserQueryProvider;
|
import org.keycloak.storage.user.UserQueryProvider;
|
||||||
import org.keycloak.storage.user.UserUpdateProvider;
|
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -35,7 +35,7 @@ public interface UserProvider extends Provider,
|
||||||
UserLookupProvider,
|
UserLookupProvider,
|
||||||
UserQueryProvider,
|
UserQueryProvider,
|
||||||
UserCredentialValidatorProvider,
|
UserCredentialValidatorProvider,
|
||||||
UserUpdateProvider {
|
UserRegistrationProvider {
|
||||||
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
||||||
|
|
||||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
|
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.storage;
|
package org.keycloak.storage;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -29,6 +30,13 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class StorageProviderModel implements Serializable {
|
public class StorageProviderModel implements Serializable {
|
||||||
|
|
||||||
|
public static Comparator<StorageProviderModel> comparator = new Comparator<StorageProviderModel>() {
|
||||||
|
@Override
|
||||||
|
public int compare(StorageProviderModel o1, StorageProviderModel o2) {
|
||||||
|
return o1.priority - o2.priority;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String providerName;
|
private String providerName;
|
||||||
private Map<String, String> config = new HashMap<String, String>();
|
private Map<String, String> config = new HashMap<String, String>();
|
||||||
|
|
|
@ -38,7 +38,7 @@ import org.keycloak.storage.user.UserLookupProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.storage.user.UserQueryProvider;
|
import org.keycloak.storage.user.UserQueryProvider;
|
||||||
import org.keycloak.storage.user.UserUpdateProvider;
|
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||||
UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class);
|
UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class);
|
||||||
if (registry != null) {
|
if (registry != null) {
|
||||||
return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions);
|
return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String username) {
|
public UserModel addUser(RealmModel realm, String username) {
|
||||||
UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class);
|
UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class);
|
||||||
if (registry != null) {
|
if (registry != null) {
|
||||||
return registry.addUser(realm, username);
|
return registry.addUser(realm, username);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ public class UserStorageManager implements UserProvider {
|
||||||
if (storageId.getProviderId() == null) {
|
if (storageId.getProviderId() == null) {
|
||||||
return localStorage().removeUser(realm, user);
|
return localStorage().removeUser(realm, user);
|
||||||
}
|
}
|
||||||
UserUpdateProvider registry = (UserUpdateProvider)getStorageProvider(realm, storageId.getProviderId());
|
UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(realm, storageId.getProviderId());
|
||||||
if (registry == null) {
|
if (registry == null) {
|
||||||
throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
|
throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
|
||||||
}
|
}
|
||||||
|
@ -446,11 +446,11 @@ public class UserStorageManager implements UserProvider {
|
||||||
@Override
|
@Override
|
||||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||||
// not federation-aware for now
|
// not federation-aware for now
|
||||||
List<UserUpdateProvider> storageProviders = getStorageProviders(realm, UserUpdateProvider.class);
|
List<UserRegistrationProvider> storageProviders = getStorageProviders(realm, UserRegistrationProvider.class);
|
||||||
LinkedList<UserUpdateProvider> providers = new LinkedList<>();
|
LinkedList<UserRegistrationProvider> providers = new LinkedList<>();
|
||||||
providers.add(localStorage());
|
providers.add(localStorage());
|
||||||
providers.addAll(storageProviders);
|
providers.addAll(storageProviders);
|
||||||
for (UserUpdateProvider provider : providers) {
|
for (UserRegistrationProvider provider : providers) {
|
||||||
provider.grantToAllUsers(realm, role);
|
provider.grantToAllUsers(realm, role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.keycloak.models.UserModel;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserUpdateProvider {
|
public interface UserRegistrationProvider {
|
||||||
UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
|
UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
|
||||||
|
|
||||||
UserModel addUser(RealmModel realm, String username);
|
UserModel addUser(RealmModel realm, String username);
|
|
@ -18,15 +18,30 @@
|
||||||
|
|
||||||
<EntitiesDescriptor Name="urn:keycloak"
|
<EntitiesDescriptor Name="urn:keycloak"
|
||||||
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<EntityDescriptor entityID="${idp.entityID}">
|
<EntityDescriptor entityID="${idp.entityID}">
|
||||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
|
|
||||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
|
<KeyDescriptor use="signing">
|
||||||
|
<dsig:KeyInfo>
|
||||||
|
<dsig:X509Data>
|
||||||
|
<dsig:X509Certificate>
|
||||||
|
${idp.signing.certificate}
|
||||||
|
</dsig:X509Certificate>
|
||||||
|
</dsig:X509Data>
|
||||||
|
</dsig:KeyInfo>
|
||||||
|
</KeyDescriptor>
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="${idp.sls.HTTP-POST}" />
|
||||||
|
<SingleLogoutService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="${idp.sso.HTTP-Redirect}" />
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
|
||||||
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
|
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
|
||||||
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
||||||
|
|
||||||
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
Location="${idp.sso.HTTP-POST}" />
|
Location="${idp.sso.HTTP-POST}" />
|
||||||
<SingleSignOnService
|
<SingleSignOnService
|
||||||
|
@ -35,21 +50,6 @@
|
||||||
<SingleSignOnService
|
<SingleSignOnService
|
||||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
||||||
Location="${idp.sso.HTTP-POST}" />
|
Location="${idp.sso.HTTP-POST}" />
|
||||||
<SingleLogoutService
|
|
||||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
||||||
Location="${idp.sls.HTTP-POST}" />
|
|
||||||
<SingleLogoutService
|
|
||||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
||||||
Location="${idp.sso.HTTP-Redirect}" />
|
|
||||||
<KeyDescriptor use="signing">
|
|
||||||
<dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
|
|
||||||
<dsig:X509Data>
|
|
||||||
<dsig:X509Certificate>
|
|
||||||
${idp.signing.certificate}
|
|
||||||
</dsig:X509Certificate>
|
|
||||||
</dsig:X509Data>
|
|
||||||
</dsig:KeyInfo>
|
|
||||||
</KeyDescriptor>
|
|
||||||
</IDPSSODescriptor>
|
</IDPSSODescriptor>
|
||||||
</EntityDescriptor>
|
</EntityDescriptor>
|
||||||
</EntitiesDescriptor>
|
</EntitiesDescriptor>
|
|
@ -29,6 +29,7 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.infinispan.UserAdapter;
|
import org.keycloak.models.cache.infinispan.UserAdapter;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
import org.keycloak.storage.StorageProviderModel;
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
@ -47,12 +48,18 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class UserFederationStorageTest {
|
public class UserFederationStorageTest {
|
||||||
|
public static StorageProviderModel memoryProvider = null;
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
StorageProviderModel model = new StorageProviderModel();
|
StorageProviderModel model = new StorageProviderModel();
|
||||||
|
model.setDisplayName("memory");
|
||||||
|
model.setPriority(0);
|
||||||
|
model.setProviderName(UserMapStorageFactory.PROVIDER_ID);
|
||||||
|
memoryProvider = appRealm.addStorageProvider(model);
|
||||||
|
model = new StorageProviderModel();
|
||||||
model.setDisplayName("read-only-user-props");
|
model.setDisplayName("read-only-user-props");
|
||||||
model.setPriority(1);
|
model.setPriority(1);
|
||||||
model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID);
|
model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID);
|
||||||
|
@ -238,4 +245,24 @@ public class UserFederationStorageTest {
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegistration() {
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
UserModel user = session.users().addUser(realm, "memuser");
|
||||||
|
user.updateCredential(UserCredentialModel.password("password"));
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
loginSuccessAndLogout("memuser", "password");
|
||||||
|
|
||||||
|
session = keycloakRule.startSession();
|
||||||
|
realm = session.realms().getRealmByName("test");
|
||||||
|
user = session.users().getUserByUsername("memuser", realm);
|
||||||
|
Assert.assertEquals(memoryProvider.getId(), StorageId.resolveProviderId(user));
|
||||||
|
Assert.assertEquals(0, user.getCredentialsDirectly().size());
|
||||||
|
session.users().removeUser(realm, user);
|
||||||
|
Assert.assertNull(session.users().getUserByUsername("memuser", realm));
|
||||||
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.keycloak.testsuite.federation.storage;
|
||||||
|
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
|
import org.keycloak.storage.StorageProvider;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||||
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
|
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UserMapStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserRegistrationProvider {
|
||||||
|
|
||||||
|
protected Map<String, String> userPasswords;
|
||||||
|
protected StorageProviderModel model;
|
||||||
|
protected KeycloakSession session;
|
||||||
|
|
||||||
|
public UserMapStorage(KeycloakSession session, StorageProviderModel model, Map<String, String> userPasswords) {
|
||||||
|
this.session = session;
|
||||||
|
this.model = model;
|
||||||
|
this.userPasswords = userPasswords;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserById(String id, RealmModel realm) {
|
||||||
|
StorageId storageId = new StorageId(id);
|
||||||
|
final String username = storageId.getStorageId();
|
||||||
|
if (!userPasswords.containsKey(username)) return null;
|
||||||
|
|
||||||
|
return createUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserModel createUser(RealmModel realm, String username) {
|
||||||
|
return new AbstractUserAdapterFederatedStorage(session, realm, model) {
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsername(String username) {
|
||||||
|
throw new RuntimeException("Unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredential(UserCredentialModel cred) {
|
||||||
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
|
userPasswords.put(username, cred.getValue());
|
||||||
|
} else {
|
||||||
|
super.updateCredential(cred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||||
|
if (!userPasswords.containsKey(username)) return null;
|
||||||
|
|
||||||
|
return createUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||||
|
userPasswords.put(username, "");
|
||||||
|
return createUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel addUser(RealmModel realm, String username) {
|
||||||
|
userPasswords.put(username, "");
|
||||||
|
return createUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||||
|
return userPasswords.remove(user.getUsername()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm, GroupModel group) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm, StorageProviderModel model) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||||
|
for (UserCredentialModel cred : input) {
|
||||||
|
if (!cred.getType().equals(UserCredentialModel.PASSWORD)) return false;
|
||||||
|
String password = (String)userPasswords.get(user.getUsername());
|
||||||
|
if (password == null) return false;
|
||||||
|
if (!password.equals(cred.getValue())) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.keycloak.testsuite.federation.storage;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.storage.StorageProvider;
|
||||||
|
import org.keycloak.storage.StorageProviderFactory;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UserMapStorageFactory implements StorageProviderFactory<UserMapStorage> {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "user-password-map";
|
||||||
|
|
||||||
|
protected Map<String, String> userPasswords = new Hashtable<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserMapStorage getInstance(KeycloakSession session, StorageProviderModel model) {
|
||||||
|
return new UserMapStorage(session, model, userPasswords);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StorageProvider create(KeycloakSession session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.keycloak.testsuite.saml;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.common.util.StreamUtil;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
|
import org.keycloak.protocol.saml.SamlService;
|
||||||
|
import org.keycloak.saml.SPMetadataDescriptor;
|
||||||
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import javax.xml.XMLConstants;
|
||||||
|
import javax.xml.transform.Source;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
import javax.xml.validation.Schema;
|
||||||
|
import javax.xml.validation.SchemaFactory;
|
||||||
|
import javax.xml.validation.Validator;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ValidationTest {
|
||||||
|
|
||||||
|
public static String getIDPMetadataDescriptor() throws IOException {
|
||||||
|
InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
|
||||||
|
String template = StreamUtil.readString(is);
|
||||||
|
template = template.replace("${idp.entityID}", "http://keycloak.org/auth/realms/test");
|
||||||
|
template = template.replace("${idp.sso.HTTP-POST}", "http://keycloak.org/auth/realms/test/saml");
|
||||||
|
template = template.replace("${idp.sso.HTTP-Redirect}", "http://keycloak.org/auth/realms/test/saml");
|
||||||
|
template = template.replace("${idp.sls.HTTP-POST}", "http://keycloak.org/auth/realms/test/saml");
|
||||||
|
template = template.replace("${idp.signing.certificate}", KeycloakModelUtils.generateKeyPairCertificate("test").getCertificate());
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore // ignore because it goes out to web
|
||||||
|
public void testIDPDescriptor() throws Exception {
|
||||||
|
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
|
||||||
|
Source xmlFile = new StreamSource(new ByteArrayInputStream(getIDPMetadataDescriptor().getBytes()), "IDPSSODescriptor");
|
||||||
|
SchemaFactory schemaFactory = SchemaFactory
|
||||||
|
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||||
|
Schema schema = schemaFactory.newSchema(schemaFile);
|
||||||
|
Validator validator = schema.newValidator();
|
||||||
|
try {
|
||||||
|
validator.validate(xmlFile);
|
||||||
|
System.out.println(xmlFile.getSystemId() + " is valid");
|
||||||
|
} catch (SAXException e) {
|
||||||
|
System.out.println(xmlFile.getSystemId() + " is NOT valid");
|
||||||
|
System.out.println("Reason: " + e.getLocalizedMessage());
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
@Ignore // ignore because it goes out to web
|
||||||
|
public void testBrokerExportDescriptor() throws Exception {
|
||||||
|
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
|
||||||
|
Source xmlFile = new StreamSource(new ByteArrayInputStream(SPMetadataDescriptor.getSPDescriptor(
|
||||||
|
"POST", "http://realm/assertion", "http://realm/logout", true, "test", SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT, KeycloakModelUtils.generateKeyPairCertificate("test").getCertificate()
|
||||||
|
).getBytes()), "SP Descriptor");
|
||||||
|
SchemaFactory schemaFactory = SchemaFactory
|
||||||
|
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||||
|
Schema schema = schemaFactory.newSchema(schemaFile);
|
||||||
|
Validator validator = schema.newValidator();
|
||||||
|
try {
|
||||||
|
validator.validate(xmlFile);
|
||||||
|
System.out.println(xmlFile.getSystemId() + " is valid");
|
||||||
|
} catch (SAXException e) {
|
||||||
|
System.out.println(xmlFile.getSystemId() + " is NOT valid");
|
||||||
|
System.out.println("Reason: " + e.getLocalizedMessage());
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory
|
org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory
|
||||||
|
org.keycloak.testsuite.federation.storage.UserMapStorageFactory
|
Loading…
Reference in a new issue