add CredentialData
This commit is contained in:
parent
04381aec20
commit
d1cad30fcb
|
@ -0,0 +1,5 @@
|
|||
package eu.eudat.authorization;
|
||||
|
||||
public class ClaimNames {
|
||||
public static final String ExternalProviderName = "ExternalProviderName";
|
||||
}
|
|
@ -3,15 +3,15 @@ package eu.eudat.commons.types.usercredential;
|
|||
import java.util.List;
|
||||
|
||||
public class UserCredentialDataEntity {
|
||||
private List<String> providers;
|
||||
private List<String> externalProviderNames;
|
||||
private String email;
|
||||
|
||||
public List<String> getProviders() {
|
||||
return providers;
|
||||
public List<String> getExternalProviderNames() {
|
||||
return externalProviderNames;
|
||||
}
|
||||
|
||||
public void setProviders(List<String> providers) {
|
||||
this.providers = providers;
|
||||
public void setExternalProviderNames(List<String> externalProviderNames) {
|
||||
this.externalProviderNames = externalProviderNames;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
|
|
|
@ -25,14 +25,12 @@ import org.springframework.context.ApplicationContext;
|
|||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class InboxRepositoryImpl implements InboxRepository {
|
||||
|
||||
protected final ApplicationContext applicationContext;
|
||||
private final Random random = new Random();
|
||||
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InboxRepositoryImpl.class));
|
||||
private final JsonHandlingService jsonHandlingService;
|
||||
private final InboxProperties inboxProperties;
|
||||
|
@ -85,18 +83,18 @@ public class InboxRepositoryImpl implements InboxRepository {
|
|||
transaction.commit();
|
||||
} catch (OptimisticLockException ex) {
|
||||
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
|
||||
this.logger.debug("Concurrency exception getting queue inbox. Skipping: {} ", ex.getMessage());
|
||||
logger.debug("Concurrency exception getting queue inbox. Skipping: {} ", ex.getMessage());
|
||||
if (transaction != null) transaction.rollback();
|
||||
candidate = null;
|
||||
} catch (Exception ex) {
|
||||
this.logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex);
|
||||
logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex);
|
||||
if (transaction != null) transaction.rollback();
|
||||
candidate = null;
|
||||
} finally {
|
||||
if (entityManager != null) entityManager.close();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
this.logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex);
|
||||
logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return candidate;
|
||||
|
|
|
@ -47,7 +47,7 @@ public class UserCredentialDataBuilder extends BaseBuilder<UserCredentialData, U
|
|||
for (UserCredentialDataEntity d : data) {
|
||||
UserCredentialData m = new UserCredentialData();
|
||||
if (fields.hasField(this.asIndexer(UserCredentialData._email))) m.setEmail(d.getEmail());
|
||||
if (fields.hasField(this.asIndexer(UserCredentialData._providers))) m.setProviders(d.getProviders());
|
||||
if (fields.hasField(this.asIndexer(UserCredentialData._externalProviderNames))) m.setExternalProviderNames(d.getExternalProviderNames());
|
||||
models.add(m);
|
||||
}
|
||||
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
|
||||
|
|
|
@ -3,17 +3,17 @@ package eu.eudat.model.usercredential;
|
|||
import java.util.List;
|
||||
|
||||
public class UserCredentialData {
|
||||
private List<String> providers;
|
||||
public static final String _providers = "providers";
|
||||
private List<String> externalProviderNames;
|
||||
public static final String _externalProviderNames = "externalProviderNames";
|
||||
private String email;
|
||||
public static final String _email = "email";
|
||||
|
||||
public List<String> getProviders() {
|
||||
return providers;
|
||||
public List<String> getExternalProviderNames() {
|
||||
return externalProviderNames;
|
||||
}
|
||||
|
||||
public void setProviders(List<String> providers) {
|
||||
this.providers = providers;
|
||||
public void setExternalProviderNames(List<String> externalProviderNames) {
|
||||
this.externalProviderNames = externalProviderNames;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
|
|
|
@ -177,6 +177,8 @@ public class UserCredentialQuery extends QueryBase<UserCredentialEntity> {
|
|||
else if (item.prefix(UserCredential._user)) return UserCredentialEntity._userId;
|
||||
else if (item.match(UserCredential._user)) return UserCredentialEntity._userId;
|
||||
else if (item.match(UserCredential._createdAt) ) return UserCredentialEntity._createdAt;
|
||||
else if (item.match(UserCredential._data) ) return UserCredentialEntity._data;
|
||||
else if (item.prefix(UserCredential._data) ) return UserCredentialEntity._data;
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
@ -187,6 +189,7 @@ public class UserCredentialQuery extends QueryBase<UserCredentialEntity> {
|
|||
item.setExternalId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._externalId, String.class));
|
||||
item.setUserId(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._userId, UUID.class));
|
||||
item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._createdAt, Instant.class));
|
||||
item.setData(QueryBase.convertSafe(tuple, columns, UserCredentialEntity._data, String.class));
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package eu.eudat.interceptors;
|
||||
|
||||
|
||||
import eu.eudat.authorization.ClaimNames;
|
||||
import eu.eudat.commons.JsonHandlingService;
|
||||
import eu.eudat.commons.enums.ContactInfoType;
|
||||
import eu.eudat.commons.enums.IsActive;
|
||||
import eu.eudat.commons.lock.LockByKeyManager;
|
||||
import eu.eudat.commons.scope.user.UserScope;
|
||||
import eu.eudat.commons.types.user.AdditionalInfoEntity;
|
||||
import eu.eudat.commons.types.usercredential.UserCredentialDataEntity;
|
||||
import eu.eudat.data.UserContactInfoEntity;
|
||||
import eu.eudat.data.UserCredentialEntity;
|
||||
import eu.eudat.data.UserEntity;
|
||||
|
@ -14,7 +16,9 @@ import eu.eudat.data.UserRoleEntity;
|
|||
import eu.eudat.model.UserContactInfo;
|
||||
import eu.eudat.model.UserCredential;
|
||||
import eu.eudat.model.UserRole;
|
||||
import eu.eudat.query.*;
|
||||
import eu.eudat.query.UserContactInfoQuery;
|
||||
import eu.eudat.query.UserCredentialQuery;
|
||||
import eu.eudat.query.UserRoleQuery;
|
||||
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
|
||||
import gr.cite.tools.data.query.QueryFactory;
|
||||
|
@ -23,6 +27,7 @@ import gr.cite.tools.fieldset.BaseFieldSet;
|
|||
import gr.cite.tools.logging.LoggerService;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
@ -35,14 +40,11 @@ import org.springframework.ui.ModelMap;
|
|||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||
|
||||
import javax.management.InvalidApplicationException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@Component
|
||||
public class UserInterceptor implements WebRequestInterceptor {
|
||||
|
@ -86,13 +88,13 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed");
|
||||
|
||||
UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
|
||||
if (cacheValue != null && emailExistsToUser(cacheValue.getEmails()) && userRolesSynced(cacheValue.getRoles())) {
|
||||
if (cacheValue != null && emailExistsToPrincipal(cacheValue.getProviderEmail()) && userRolesSynced(cacheValue.getRoles()) && providerExistsToPrincipal(cacheValue.getExternalProviderNames())) {
|
||||
userId = cacheValue.getUserId();
|
||||
} else {
|
||||
boolean usedResource = false;
|
||||
try {
|
||||
usedResource = this.lockByKeyManager.tryLock(subjectId, 5000, TimeUnit.MILLISECONDS);
|
||||
String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal());
|
||||
String email = this.getEmailFromClaims();
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setName(UUID.randomUUID().toString());
|
||||
|
@ -101,14 +103,18 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
TransactionStatus status = null;
|
||||
try {
|
||||
status = transactionManager.getTransaction(definition);
|
||||
|
||||
userId = this.findExistingUserFromDb(subjectId);
|
||||
boolean isNewUser = userId == null;
|
||||
if (isNewUser) {
|
||||
UserEntity user = this.addNewUser(subjectId, email);
|
||||
userId = user.getId();
|
||||
}
|
||||
this.entityManager.flush();
|
||||
|
||||
if (!isNewUser) this.syncUserWithClaims(userId);
|
||||
if (!isNewUser){
|
||||
this.syncUserWithClaims(userId, subjectId);
|
||||
}
|
||||
|
||||
this.entityManager.flush();
|
||||
transactionManager.commit(status);
|
||||
|
@ -118,9 +124,13 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
}
|
||||
|
||||
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
|
||||
cacheValue.setEmails(new ArrayList<>());
|
||||
if (email != null && !email.isBlank()) cacheValue.getEmails().add(email);
|
||||
cacheValue.setRoles(claimExtractor.roles(currentPrincipalResolver.currentPrincipal()));
|
||||
cacheValue.setRoles(this.getRolesFromClaims());
|
||||
if (email != null && !email.isBlank()) cacheValue.setProviderEmail(email);
|
||||
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._data));
|
||||
if (userCredential != null && userCredential.getData() != null){
|
||||
UserCredentialDataEntity userCredentialDataEntity = this.jsonHandlingService.fromJsonSafe(UserCredentialDataEntity.class, userCredential.getData());
|
||||
if (userCredentialDataEntity != null) cacheValue.setExternalProviderNames(userCredentialDataEntity.getExternalProviderNames());
|
||||
}
|
||||
|
||||
this.userInterceptorCacheService.put(cacheValue);
|
||||
} finally {
|
||||
|
@ -132,11 +142,10 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
this.userScope.setUserId(userId);
|
||||
}
|
||||
|
||||
private void syncUserWithClaims(UUID userId){
|
||||
private void syncUserWithClaims(UUID userId, String subjectId){
|
||||
List<String> existingUserEmails = this.collectUserEmails(userId);
|
||||
List<String> existingUserRoles = this.collectUserRoles(userId);
|
||||
if (!this.emailExistsToUser(existingUserEmails)){
|
||||
String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal());
|
||||
if (!this.containsPrincipalEmail(existingUserEmails)){
|
||||
String email = this.getEmailFromClaims();
|
||||
long contactUsedByOthersCount = this.queryFactory.query(UserContactInfoQuery.class).excludedUserIds(userId).types(ContactInfoType.Email).values(email).count();
|
||||
if (contactUsedByOthersCount > 0) {
|
||||
logger.warn("user contact exists to other user" + email);
|
||||
|
@ -148,9 +157,37 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
}
|
||||
}
|
||||
|
||||
List<String> existingUserRoles = this.collectUserRoles(userId);
|
||||
if (!this.userRolesSynced(existingUserRoles)){
|
||||
this.syncRoles(userId);
|
||||
}
|
||||
|
||||
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).first();
|
||||
if (userCredential == null) {
|
||||
throw new MyForbiddenException("UserCredential not found");
|
||||
}
|
||||
else {
|
||||
boolean updatedUserCredential = false;
|
||||
UserCredentialDataEntity userCredentialDataEntity = this.jsonHandlingService.fromJsonSafe(UserCredentialDataEntity.class, userCredential.getData());
|
||||
if (userCredentialDataEntity == null) userCredentialDataEntity = new UserCredentialDataEntity();
|
||||
if (userCredentialDataEntity.getExternalProviderNames() == null) userCredentialDataEntity.setExternalProviderNames(new ArrayList<>());
|
||||
|
||||
String email = this.getEmailFromClaims();
|
||||
String provider = this.getProviderFromClaims();
|
||||
|
||||
if (email != null && !email.equalsIgnoreCase(userCredentialDataEntity.getEmail())) {
|
||||
userCredentialDataEntity.setEmail(email);
|
||||
updatedUserCredential = true;
|
||||
}
|
||||
if (provider != null && !provider.isBlank() && userCredentialDataEntity.getExternalProviderNames().stream().noneMatch(provider::equalsIgnoreCase)) {
|
||||
userCredentialDataEntity.getExternalProviderNames().add(provider);
|
||||
updatedUserCredential = true;
|
||||
}
|
||||
if (updatedUserCredential) {
|
||||
userCredential.setData(this.jsonHandlingService.toJsonSafe(userCredentialDataEntity));
|
||||
this.entityManager.persist(userCredential);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UUID findExistingUserFromDb(String subjectId){
|
||||
|
@ -158,7 +195,7 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
if (userCredential != null) {
|
||||
return userCredential.getUserId();
|
||||
} else {
|
||||
String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal());
|
||||
String email = this.getEmailFromClaims();
|
||||
if (email != null && !email.isBlank()) {
|
||||
UserContactInfoEntity userContactInfo = this.queryFactory.query(UserContactInfoQuery.class).types(ContactInfoType.Email).values(email).firstAs(new BaseFieldSet().ensure(UserContactInfo._user));
|
||||
if (userContactInfo != null) {
|
||||
|
@ -174,14 +211,17 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void syncRoles(UUID userId){
|
||||
private List<String> getRolesFromClaims(){
|
||||
List<String> claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
|
||||
if (claimsRoles == null) claimsRoles = new ArrayList<>();
|
||||
claimsRoles = claimsRoles.stream().filter(x-> x != null && !x.isBlank()).distinct().toList();
|
||||
return claimsRoles;
|
||||
}
|
||||
|
||||
private void syncRoles(UUID userId){
|
||||
List<UserRoleEntity> existingUserRoles = this.queryFactory.query(UserRoleQuery.class).userIds(userId).collect();
|
||||
List<UUID> foundRoles = new ArrayList<>();
|
||||
for (String claimRole : claimsRoles) {
|
||||
for (String claimRole : this.getRolesFromClaims()) {
|
||||
UserRoleEntity roleEntity = existingUserRoles.stream().filter(x-> x.getRole().equals(claimRole)).findFirst().orElse(null);
|
||||
if (roleEntity == null) {
|
||||
roleEntity = this.buildRole(userId, claimRole);
|
||||
|
@ -206,17 +246,26 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
return items == null ? new ArrayList<>() : items.stream().map(UserContactInfoEntity::getValue).toList();
|
||||
}
|
||||
|
||||
private boolean emailExistsToUser(List<String> existingUserEmails){
|
||||
String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal());
|
||||
private boolean containsPrincipalEmail(List<String> existingUserEmails){
|
||||
String email = this.getEmailFromClaims();
|
||||
return email == null || email.isBlank() ||
|
||||
(existingUserEmails != null && existingUserEmails.stream().anyMatch(email::equals));
|
||||
}
|
||||
private boolean emailExistsToPrincipal(String existingUserEmail){
|
||||
String email = this.getEmailFromClaims();
|
||||
return email == null || email.isBlank() || email.equalsIgnoreCase(existingUserEmail);
|
||||
}
|
||||
|
||||
private boolean providerExistsToPrincipal(List<String> principalCredentialProviders){
|
||||
String provider = this.getProviderFromClaims();
|
||||
return provider == null || provider.isBlank() ||
|
||||
(principalCredentialProviders != null && principalCredentialProviders.stream().anyMatch(provider::equalsIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
private boolean userRolesSynced(List<String> existingUserRoles){
|
||||
List<String> claimsRoles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
|
||||
if (claimsRoles == null) claimsRoles = new ArrayList<>();
|
||||
List<String> claimsRoles = this.getRolesFromClaims();
|
||||
if (existingUserRoles == null) existingUserRoles = new ArrayList<>();
|
||||
claimsRoles = claimsRoles.stream().filter(x-> x != null && !x.isBlank()).distinct().toList();
|
||||
existingUserRoles = existingUserRoles.stream().filter(x-> x != null && !x.isBlank()).distinct().toList();
|
||||
if (claimsRoles.size() != existingUserRoles.size()) return false;
|
||||
|
||||
|
@ -226,8 +275,28 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
return true;
|
||||
}
|
||||
|
||||
private String getEmailFromClaims(){
|
||||
String email = this.claimExtractor.email(this.currentPrincipalResolver.currentPrincipal());
|
||||
if (email == null || email.isBlank() || !EmailValidator.getInstance().isValid(email)) return null;
|
||||
return email.trim();
|
||||
}
|
||||
|
||||
private String getProviderFromClaims(){
|
||||
String provider = this.claimExtractor.asString(this.currentPrincipalResolver.currentPrincipal(), ClaimNames.ExternalProviderName);
|
||||
if (provider == null || provider.isBlank()) return null;
|
||||
return provider.trim();
|
||||
}
|
||||
|
||||
private UserCredentialEntity buildCredential(UUID userId, String subjectId){
|
||||
UserCredentialEntity data = new UserCredentialEntity();
|
||||
UserCredentialDataEntity userCredentialDataEntity = new UserCredentialDataEntity();
|
||||
|
||||
String email = this.getEmailFromClaims();
|
||||
String provider = this.getProviderFromClaims();
|
||||
if (email != null && !email.isBlank()) userCredentialDataEntity.setEmail(email);
|
||||
if (provider != null && !provider.isBlank()) userCredentialDataEntity.setExternalProviderNames(List.of(provider));
|
||||
data.setData(this.jsonHandlingService.toJsonSafe(userCredentialDataEntity));
|
||||
|
||||
data.setId(UUID.randomUUID());
|
||||
data.setUserId(userId);
|
||||
data.setCreatedAt(Instant.now());
|
||||
|
@ -258,7 +327,7 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
|
||||
|
||||
private UserEntity addNewUser(String subjectId, String email){
|
||||
List<String> roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
|
||||
List<String> roles = this.getRolesFromClaims();
|
||||
String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal());
|
||||
|
||||
UserEntity user = new UserEntity();
|
||||
|
@ -267,7 +336,7 @@ public class UserInterceptor implements WebRequestInterceptor {
|
|||
user.setCreatedAt(Instant.now());
|
||||
user.setUpdatedAt(Instant.now());
|
||||
user.setIsActive(IsActive.Active);
|
||||
user.setAdditionalInfo(this.jsonHandlingService.toJsonSafe(new AdditionalInfoEntity()));
|
||||
user.setAdditionalInfo(this.jsonHandlingService.toJsonSafe(new AdditionalInfoEntity())); //TODO
|
||||
this.entityManager.persist(user);
|
||||
|
||||
UserCredentialEntity credential = this.buildCredential(user.getId(), subjectId);
|
||||
|
|
|
@ -35,7 +35,8 @@ public class UserInterceptorCacheService extends CacheService<UserInterceptorCac
|
|||
private String subjectId;
|
||||
private UUID userId;
|
||||
private List<String> roles;
|
||||
private List<String> emails;
|
||||
private String providerEmail;
|
||||
private List<String> externalProviderNames;
|
||||
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
|
@ -53,12 +54,20 @@ public class UserInterceptorCacheService extends CacheService<UserInterceptorCac
|
|||
this.roles = roles;
|
||||
}
|
||||
|
||||
public List<String> getEmails() {
|
||||
return emails;
|
||||
public String getProviderEmail() {
|
||||
return providerEmail;
|
||||
}
|
||||
|
||||
public void setEmails(List<String> emails) {
|
||||
this.emails = emails;
|
||||
public void setProviderEmail(String providerEmail) {
|
||||
this.providerEmail = providerEmail;
|
||||
}
|
||||
|
||||
public List<String> getExternalProviderNames() {
|
||||
return externalProviderNames;
|
||||
}
|
||||
|
||||
public void setExternalProviderNames(List<String> externalProviderNames) {
|
||||
this.externalProviderNames = externalProviderNames;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,3 +37,5 @@ idpclient:
|
|||
- type: azp
|
||||
Authorities:
|
||||
- type: authorities
|
||||
ExternalProviderName:
|
||||
- type: identity_provider
|
|
@ -8,6 +8,7 @@ BEGIN
|
|||
(
|
||||
id uuid NOT NULL,
|
||||
"user" uuid NOT NULL,
|
||||
"data" character varying NULL,
|
||||
external_id character varying(512) NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
|
|
Loading…
Reference in New Issue