diff --git a/.gitignore b/.gitignore index 76515a1..5cb71cd 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,14 @@ fabric.properties .idea +keycloak-member-storage-jpa/build + +.DS_Store + +.gradle/ +**/.gradle/ +.gradle +**/build/ +!src/**/build/ +*.gradle + diff --git a/.gradle/7.4/checksums/checksums.lock b/.gradle/7.4/checksums/checksums.lock deleted file mode 100644 index fa1122b..0000000 Binary files a/.gradle/7.4/checksums/checksums.lock and /dev/null differ diff --git a/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock b/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock deleted file mode 100644 index b80172e..0000000 Binary files a/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock and /dev/null differ diff --git a/.gradle/7.4/executionHistory/executionHistory.bin b/.gradle/7.4/executionHistory/executionHistory.bin deleted file mode 100644 index 890d90f..0000000 Binary files a/.gradle/7.4/executionHistory/executionHistory.bin and /dev/null differ diff --git a/.gradle/7.4/executionHistory/executionHistory.lock b/.gradle/7.4/executionHistory/executionHistory.lock deleted file mode 100644 index 46acbe1..0000000 Binary files a/.gradle/7.4/executionHistory/executionHistory.lock and /dev/null differ diff --git a/.gradle/7.4/fileChanges/last-build.bin b/.gradle/7.4/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/.gradle/7.4/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/7.4/fileHashes/fileHashes.bin b/.gradle/7.4/fileHashes/fileHashes.bin deleted file mode 100644 index 9bec368..0000000 Binary files a/.gradle/7.4/fileHashes/fileHashes.bin and /dev/null differ diff --git a/.gradle/7.4/fileHashes/fileHashes.lock b/.gradle/7.4/fileHashes/fileHashes.lock deleted file mode 100644 index b1cfdfc..0000000 Binary files a/.gradle/7.4/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/7.4/gc.properties b/.gradle/7.4/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 4a26bb5..0000000 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index b009fa9..0000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Sat Apr 15 14:28:04 KST 2023 -gradle.version=7.4 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 2757823..0000000 Binary files a/.gradle/buildOutputCleanup/outputFiles.bin and /dev/null differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe deleted file mode 100644 index fb3c9e0..0000000 Binary files a/.gradle/file-system.probe and /dev/null differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/keycloak-user-storage-jpa/conf/quarkus.properties b/keycloak-user-storage-jpa/conf/quarkus.properties new file mode 100644 index 0000000..9e1b792 --- /dev/null +++ b/keycloak-user-storage-jpa/conf/quarkus.properties @@ -0,0 +1,4 @@ +quarkus.datasource.user-store.db-kind=mysql +quarkus.datasource.user-store.username=root +quarkus.datasource.user-store.password=test1234 +quarkus.datasource.user-store.jdbc.url=jdbc:mysql://localhost:3306/test diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/MyUserStorageProvider.java b/keycloak-user-storage-jpa/src/main/java/org/mju/MyUserStorageProvider.java new file mode 100644 index 0000000..00179fc --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/MyUserStorageProvider.java @@ -0,0 +1,276 @@ +package org.mju; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputUpdater; +import org.keycloak.credential.CredentialInputValidator; +import org.keycloak.credential.CredentialModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.cache.OnUserCache; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserQueryProvider; +import org.keycloak.storage.user.UserRegistrationProvider; +import org.mju.domain.UserAdapter; +import org.mju.domain.UserEntity; + +public class MyUserStorageProvider implements UserStorageProvider, + UserLookupProvider, + UserRegistrationProvider, + UserQueryProvider, + CredentialInputUpdater, + CredentialInputValidator, + OnUserCache { + private static final Logger logger = Logger.getLogger(MyUserStorageProvider.class); + public static final String PASSWORD_CACHE_KEY = UserAdapter.class.getName() + ".password"; + + protected EntityManager em; + protected ComponentModel model; + protected KeycloakSession session; + + MyUserStorageProvider(final KeycloakSession session, final ComponentModel model) { + this.session = session; + this.model = model; + em = session.getProvider(JpaConnectionProvider.class, "user-store").getEntityManager(); + } + + @Override + public void close() { + logger.info("close"); + } + + @Override + public UserModel getUserById(final RealmModel realmModel, final String id) { + logger.info("getUserById: " + id); + String persistenceId = StorageId.externalId(id); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) { + logger.info("could not find user by id: " + id); + return null; + } + return new UserAdapter(session, realmModel, model, entity); + } + + @Override + public UserModel getUserByUsername(final RealmModel realmModel, final String username) { + logger.info("getUserByUsername: " + username); + TypedQuery query = em.createNamedQuery("getUserByUsername", UserEntity.class); + query.setParameter("username", username); + List result = query.getResultList(); + if (result.isEmpty()) { + logger.info("could not find username: " + username); + return null; + } + return new UserAdapter(session, realmModel, model, result.get(0)); + } + + @Override + public UserModel getUserByEmail(final RealmModel realmModel, final String email) { + logger.info("getUserByEmail: " + email); + TypedQuery query = em.createNamedQuery("getUserByEmail", UserEntity.class); + query.setParameter("email", email); + List result = query.getResultList(); + if (result.isEmpty()) { + return null; + } + return new UserAdapter(session, realmModel, model, result.get(0)); + } + + @Override + public UserModel addUser(final RealmModel realm, final String username) { + try { + Constructor declaredConstructor = UserEntity.class.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + UserEntity entity = declaredConstructor.newInstance(); + setFieldBy(entity, "username", username); + em.persist(entity); + logger.info("added user: " + username); + declaredConstructor.setAccessible(false); + return new UserAdapter(session, realm, model, entity); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private static void setFieldBy(final UserEntity entity, final String name, final String value) { + try { + logger.info("setFieldBy name: " + name + " value: " + value); + Field field = entity.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(entity, value); + field.setAccessible(false); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean removeUser(final RealmModel realm, final UserModel user) { + logger.info("removeUser: " + user.getId()); + String persistenceId = StorageId.externalId(user.getId()); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) { + return false; + } + em.remove(entity); + return true; + } + + @Override + public void onCache(final RealmModel realm, final CachedUserModel user, final UserModel delegate) { + String password = ((UserAdapter) delegate).getPassword(); + if (password != null) { + user.getCachedWith().put(PASSWORD_CACHE_KEY, password); + } + } + + @Override + public boolean supportsCredentialType(final String credentialType) { + return CredentialModel.PASSWORD.equals(credentialType); + } + + @Override + public boolean updateCredential(final RealmModel realm, final UserModel user, final CredentialInput input) { + if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) { + return false; + } + UserCredentialModel cred = (UserCredentialModel) input; + UserAdapter adapter = getUserAdapter(user); + adapter.setPassword(cred.getValue()); + return true; + } + + public UserAdapter getUserAdapter(final UserModel user) { + UserAdapter adapter = null; + if (user instanceof CachedUserModel) { + adapter = (UserAdapter) ((CachedUserModel) user).getDelegateForUpdate(); + } else { + adapter = (UserAdapter) user; + } + return adapter; + } + + @Override + public void disableCredentialType(final RealmModel realm, final UserModel user, final String credentialType) { + if (!supportsCredentialType(credentialType)) { + return; + } + getUserAdapter(user).setPassword(null); + } + + @Override + public Stream getDisableableCredentialTypesStream(final RealmModel realm, final UserModel user) { + if (getUserAdapter(user).getPassword() != null) { + Stream set = Stream.of(CredentialModel.PASSWORD); + return set; + } else { + return Stream.of(); + } + } + + @Override + public boolean isConfiguredFor(final RealmModel realm, final UserModel user, final String credentialType) { + return supportsCredentialType(credentialType) && getPassword(user) != null; + } + + @Override + public boolean isValid(final RealmModel realm, final UserModel user, final CredentialInput input) { + if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) { + return false; + } + UserCredentialModel cred = (UserCredentialModel) input; + String password = getPassword(user); + return password != null && password.equals(cred.getValue()); + } + + public String getPassword(final UserModel user) { + String password = null; + if (user instanceof CachedUserModel) { + password = (String) ((CachedUserModel) user).getCachedWith().get(PASSWORD_CACHE_KEY); + } else if (user instanceof UserAdapter) { + password = ((UserAdapter) user).getPassword(); + } + return password; + } + + @Override + public int getUsersCount(final RealmModel realm) { + return ((Number) em.createNamedQuery("getUserCount").getSingleResult()).intValue(); + } + + @Override + public int getUsersCount(final RealmModel realm, final Set groupIds) { + return UserQueryProvider.super.getUsersCount(realm, groupIds); + } + + @Override + public Stream searchForUserStream( + final RealmModel realmModel, + final String search, + final Integer firstResult, + final Integer maxResults + ) { + TypedQuery query = em.createNamedQuery("searchForUser", UserEntity.class); + query.setParameter("search", "%" + search.toLowerCase() + "%"); + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResults != -1) { + query.setMaxResults(maxResults); + } + List results = query.getResultList(); + List users = new LinkedList<>(); + for (UserEntity entity : results) { + users.add(new UserAdapter(session, realmModel, model, entity)); + } + return users.stream(); + } + + @Override + public Stream searchForUserStream( + final RealmModel realmModel, + final Map map, + final Integer firstResult, + final Integer maxResults + ) { + return searchForUserStream(realmModel, "", firstResult, maxResults); + } + + @Override + public Stream getGroupMembersStream( + final RealmModel realmModel, + final GroupModel groupModel, + final Integer integer, + final Integer integer1 + ) { + return Stream.empty(); + } + + @Override + public Stream searchForUserByUserAttributeStream( + final RealmModel realmModel, + final String s, + final String s1 + ) { + return Stream.empty(); + } +} diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/MyUserStorageProviderFactory.java b/keycloak-user-storage-jpa/src/main/java/org/mju/MyUserStorageProviderFactory.java new file mode 100644 index 0000000..d1209e5 --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/MyUserStorageProviderFactory.java @@ -0,0 +1,33 @@ +package org.mju; + +import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.storage.UserStorageProviderFactory; + +public class MyUserStorageProviderFactory implements UserStorageProviderFactory { + public static final String PROVIDER_ID = "user-storage-jpa"; + + private static final Logger logger = Logger.getLogger(MyUserStorageProviderFactory.class); + + @Override + public MyUserStorageProvider create(KeycloakSession session, ComponentModel model) { + return new MyUserStorageProvider(session, model); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getHelpText() { + return "JPA Example User Storage Provider"; + } + + @Override + public void close() { + logger.info("<<<<<< Closing factory"); + + } +} diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/domain/AdditionalInformation.java b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/AdditionalInformation.java new file mode 100644 index 0000000..1d0bb5f --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/AdditionalInformation.java @@ -0,0 +1,59 @@ +package org.mju.domain; + +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; + +@Embeddable +public class AdditionalInformation { + private String nickname; + + private String phoneNumber; + + private String address; + + @Enumerated(value = EnumType.STRING) + private Gender gender; + + @Embedded + private Birth birth = new Birth(); + + protected AdditionalInformation() { + + } + + public AdditionalInformation( + final String nickname, + final String phoneNumber, + final String address, + final Gender gender, + final Birth birth + ) { + this.nickname = nickname; + this.phoneNumber = phoneNumber; + this.address = address; + this.gender = gender; + this.birth = birth; + } + + public String getNickname() { + return nickname; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public String getAddress() { + return address; + } + + public Gender getGender() { + return gender; + } + + public Birth getBirth() { + return birth; + } +} diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/domain/Birth.java b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/Birth.java new file mode 100644 index 0000000..0ae014e --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/Birth.java @@ -0,0 +1,22 @@ +package org.mju.domain; + +import java.time.LocalDate; +import javax.persistence.Embeddable; + +@Embeddable +public class Birth { + private LocalDate localDate; + + protected Birth() { + localDate = LocalDate.now(); + } + + public Birth(LocalDate localDate) { + this.localDate = localDate; + } + + @Override + public String toString() { + return localDate.toString(); + } +} diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/domain/Gender.java b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/Gender.java new file mode 100644 index 0000000..90b60e0 --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/Gender.java @@ -0,0 +1,5 @@ +package org.mju.domain; + +public enum Gender { + MALE, FEMALE +} diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/domain/UserAdapter.java b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/UserAdapter.java new file mode 100644 index 0000000..1ab3110 --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/UserAdapter.java @@ -0,0 +1,147 @@ +/* + * 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.mju.domain; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jboss.logging.Logger; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; + +public class UserAdapter extends AbstractUserAdapterFederatedStorage { + private static final Logger logger = Logger.getLogger(UserAdapter.class); + private static final String ENABLED_FIELD_NAME = "ENABLED"; + private static final String EMAIL_VERIFIED_FIELD_NAME = "EMAIL_VERIFIED"; + private static final String PASSWORD_FIELD_NAME = "password"; + private static final String USERNAME_FIELD_NAME = "username"; + + protected UserEntity entity; + protected String keycloakId; + + public UserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, UserEntity entity) { + super(session, realm, model); + this.entity = entity; + keycloakId = StorageId.keycloakId(model, entity.getId()); + } + + public String getPassword() { + return entity.getPassword(); + } + + public void setPassword(String password) { + setFieldValueByName(PASSWORD_FIELD_NAME, password); + } + + @Override + public String getUsername() { + return entity.getUsername(); + } + + @Override + public void setUsername(String username) { + setFieldValueByName(USERNAME_FIELD_NAME, username); + } + + @Override + public String getId() { + return keycloakId; + } + + @Override + public void setSingleAttribute(String name, String value) { + setFieldValueByName(name, value); + } + + @Override + public void setAttribute(final String name, final List values) { + super.setAttribute(name, values); + } + + private void setFieldValueByName(final String name, final String value) { + try { + if (isKeycloakBasicInformationField(name)) { + Field field = entity.getClass().getDeclaredField(name); + setFieldBy(field, value); + super.setSingleAttribute(name, value); + } else if (isAdditionalInformationField(name)) { + Field field = entity.getAdditionalInformation().getClass().getDeclaredField(name); + setFieldBy(field, value); + } else { + if (name.equals(ENABLED_FIELD_NAME) || name.equals(EMAIL_VERIFIED_FIELD_NAME)) { + super.setSingleAttribute(name, value); + } else { + logger.error("Set field Error name : " + name + " value : " + value); + } + } + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + private boolean isKeycloakBasicInformationField(final String name) { + return Arrays.stream(entity.getClass().getDeclaredFields()) + .anyMatch(f -> f.getName().equals(name)); + } + + private boolean isAdditionalInformationField(final String name) { + return Arrays.stream(entity.getAdditionalInformation().getClass().getDeclaredFields()) + .anyMatch(f -> f.getName().equals(name)); + } + + private void setFieldBy(final Field field, final String value) { + try { + field.setAccessible(true); + field.set(entity, value); + field.setAccessible(false); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void removeAttribute(String name) { + setFieldValueByName(name, null); + } + + @Override + public Map> getAttributes() { + Map> attrs = super.getAttributes(); + MultivaluedHashMap all = new MultivaluedHashMap<>(); + all.putAll(attrs); + AdditionalInformation additionalInformation = entity.getAdditionalInformation(); + for (Field field : additionalInformation.getClass().getDeclaredFields()) { + field.setAccessible(true); + try { + String key = field.getName(); + String value = Optional.ofNullable(field.get(additionalInformation)).orElse("").toString(); + all.put(key, Collections.singletonList(value)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + field.setAccessible(false); + } + return all; + } +} diff --git a/keycloak-user-storage-jpa/src/main/java/org/mju/domain/UserEntity.java b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/UserEntity.java new file mode 100644 index 0000000..3ae9ccb --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/java/org/mju/domain/UserEntity.java @@ -0,0 +1,69 @@ +package org.mju.domain; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import org.hibernate.annotations.GenericGenerator; + +@NamedQueries({ + @NamedQuery(name = "getUserByUsername", query = "select u from UserEntity u where u.username = :username"), + @NamedQuery(name = "getUserByEmail", query = "select u from UserEntity u where u.email = :email"), + @NamedQuery(name = "getUserCount", query = "select count(u) from UserEntity u"), + @NamedQuery(name = "getAllUsers", query = "select u from UserEntity u"), + @NamedQuery(name = "searchForUser", query = "select u from UserEntity u where " + + "( lower(u.username) like :search or u.email like :search ) order by u.username"), +}) +@Entity +public class UserEntity { + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name = "uuid2", strategy = "uuid2") + @Id + private String id; + + private String username; + + private String email; + + private String password; + + @Embedded + private AdditionalInformation additionalInformation = new AdditionalInformation(); + + protected UserEntity() { + } + + public UserEntity( + final String username, + final String email, + final String password, + final AdditionalInformation additionalInformation + ) { + this.username = username; + this.email = email; + this.password = password; + this.additionalInformation = additionalInformation; + } + + public String getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public String getUsername() { + return username; + } + + public AdditionalInformation getAdditionalInformation() { + return additionalInformation; + } +} diff --git a/.gradle/7.4/dependencies-accessors/gc.properties b/keycloak-user-storage-jpa/src/main/resources/META-INF/beans.xml similarity index 100% rename from .gradle/7.4/dependencies-accessors/gc.properties rename to keycloak-user-storage-jpa/src/main/resources/META-INF/beans.xml diff --git a/keycloak-user-storage-jpa/src/main/resources/META-INF/persistence.xml b/keycloak-user-storage-jpa/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..24b8306 --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,18 @@ + + + + org.mju.domain.UserEntity + + + + + + + + + + diff --git a/keycloak-user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/keycloak-user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 0000000..fa5b7b4 --- /dev/null +++ b/keycloak-user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +org.mju.MyUserStorageProviderFactory diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index bcf58d8..0000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'sso' -