diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b144c87..02635d2 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,11 +11,11 @@ jobs: if: "!contains(github.event.head_commit.message, '[ci skip]')" steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: 11 + java-version: 17 cache: 'maven' server-id: ossrh server-username: OSSRH_JIRA_USERNAME diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d62e164..d77fbaf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,11 +12,11 @@ jobs: if: "!contains(github.event.head_commit.message, '[ci skip]')" steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: 11 + java-version: 17 cache: 'maven' server-id: ossrh server-username: OSSRH_JIRA_USERNAME diff --git a/pom.xml b/pom.xml index c49f284..3275db5 100644 --- a/pom.xml +++ b/pom.xml @@ -50,12 +50,18 @@ - 4.4 - 2.5.2 - 1.42.0 - 3.10.0 + 4.8 + 1.55.1 + 3.22.3 + + 5.1.2 + + 17 UTF-8 5.8.1 @@ -92,7 +98,7 @@ io.github.lognet grpc-spring-boot-starter - 4.5.10 + ${version.grpc-spring-boot-starter} org.springframework.boot @@ -112,34 +118,20 @@ com.evolveum.midpoint.gui admin-gui ${version.midpoint} - jar - classes com.google.protobuf protobuf-java ${version.protoc} - - + - org.springframework.boot - spring-boot-starter-oauth2-resource-server - ${version.spring-boot} - - - org.springframework.boot - spring-boot-starter - - - org.springframework.security - spring-security-config - - - org.springframework.security - spring-security-core - - + javax.annotation + javax.annotation-api + 1.3.2 @@ -251,7 +243,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.5.1 maven-deploy-plugin diff --git a/self-services/pom.xml b/self-services/pom.xml index b4aab80..6907e81 100644 --- a/self-services/pom.xml +++ b/self-services/pom.xml @@ -23,8 +23,6 @@ com.evolveum.midpoint.gui admin-gui - jar - classes provided @@ -37,6 +35,11 @@ protobuf-java provided + + javax.annotation + javax.annotation-api + provided + org.junit.jupiter @@ -48,19 +51,6 @@ junit-jupiter-engine test - - - net.tirasa.connid - connector-framework - 1.5.0.18 - test - - - net.tirasa.connid - connector-framework-internal - 1.5.0.18 - test - diff --git a/self-services/src/main/java/jp/openstandia/midpoint/grpc/SelfServiceResource.java b/self-services/src/main/java/jp/openstandia/midpoint/grpc/SelfServiceResource.java index ed0b68e..108dcf4 100644 --- a/self-services/src/main/java/jp/openstandia/midpoint/grpc/SelfServiceResource.java +++ b/self-services/src/main/java/jp/openstandia/midpoint/grpc/SelfServiceResource.java @@ -1,9 +1,15 @@ package jp.openstandia.midpoint.grpc; +import com.evolveum.midpoint.authentication.api.config.MidpointAuthentication; +import com.evolveum.midpoint.authentication.api.evaluator.AuthenticationEvaluator; +import com.evolveum.midpoint.authentication.api.evaluator.context.NonceAuthenticationContext; +import com.evolveum.midpoint.authentication.impl.factory.channel.SelfRegistrationChannelFactory; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.model.api.*; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.ModelInteractionService; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.api.TaskService; import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.api.context.NonceAuthenticationContext; import com.evolveum.midpoint.model.impl.ModelCrudService; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; @@ -25,10 +31,10 @@ import com.evolveum.midpoint.schema.util.LocalizationUtil; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +import com.evolveum.midpoint.schema.util.SecurityPolicyUtil; import com.evolveum.midpoint.security.api.ConnectionEnvironment; import com.evolveum.midpoint.security.api.SecurityContextManager; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskCategory; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.*; import com.evolveum.midpoint.util.exception.*; @@ -47,7 +53,9 @@ import org.lognet.springboot.grpc.GRpcService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; import javax.xml.namespace.QName; import java.util.*; @@ -114,7 +122,9 @@ public class SelfServiceResource extends SelfServiceResourceGrpc.SelfServiceReso @Autowired protected TaskService taskService; @Autowired - protected AuthenticationEvaluator nonceAuthenticationEvaluator; + protected AuthenticationEvaluator nonceAuthenticationEvaluator; + @Autowired + protected SelfRegistrationChannelFactory selfRegistrationChannelFactory; @Autowired protected SecurityContextManager securityContextManager; @Autowired @@ -204,7 +214,7 @@ public void modifyProfile(ModifyProfileRequest request, StreamObserver noncePolicy = securityPolicy.getCredentials().getNonce().stream() @@ -489,9 +501,18 @@ protected String checkUserNonce(FocusType focus, String nonce, String nonceName, .findFirst(); ConnectionEnvironment connEnv = ConnectionEnvironment.create(SchemaConstants.CHANNEL_SELF_REGISTRATION_URI); + connEnv.setSequenceIdentifier("grpc"); + connEnv.setModuleIdentifier("nonce"); + // Don't call authenticate method here because the user may not yet be active - nonceAuthenticationEvaluator.checkCredentials(connEnv, new NonceAuthenticationContext(focus.getName().getOrig(), focus.getClass(), - nonce, noncePolicy.orElse(null))); + NonceAuthenticationContext nonceAuthenticationContext = new NonceAuthenticationContext(focus.getName().getOrig(), focus.getClass(), + nonce, Collections.emptyList(), selfRegistrationChannelFactory.createAuthChannel(new AuthenticationSequenceChannelType())); + nonceAuthenticationContext.setPolicy(noncePolicy.orElse(null)); + + // Since it needs MidpointAuthentication for the authentication, we set dummy authentication + SecurityContextHolder.getContext().setAuthentication(new MidpointAuthentication(SecurityPolicyUtil.createDefaultSequence())); + + nonceAuthenticationEvaluator.authenticate(connEnv, nonceAuthenticationContext); return null; } catch (AuthenticationCredentialsNotFoundException e) { @@ -512,9 +533,38 @@ protected String checkUserNonce(FocusType focus, String nonce, String nonceName, } catch (AuthenticationException e) { LOGGER.error("Unexpected checkUserNonce error. focus: {}, nonceName: {}", focus.getOid(), nonceName, e); throw e; + } finally { + SecurityContextHolder.getContext().setAuthentication(origAuthentication); } } +// private boolean checkCredentials(MidPointPrincipal principal, T authnCtx, ConnectionEnvironment connEnv) { +// +// FocusType focusType = principal.getFocus(); +// CredentialsType credentials = focusType.getCredentials(); +// if (credentials == null || getCredential(credentials) == null) { +// recordModuleAuthenticationFailure(principal.getUsername(), principal, connEnv, getCredentialsPolicy(principal, authnCtx), "no credentials in user"); +// throw new AuthenticationCredentialsNotFoundException(AuthUtil.generateBadCredentialsMessageKey(SecurityContextHolder.getContext().getAuthentication())); +// } +// +// CredentialPolicyType credentialsPolicy = getCredentialsPolicy(principal, authnCtx); +// +// // Lockout +// if (isLockedOut(getAuthenticationData(principal, connEnv), credentialsPolicy)) { +// Authentication auth = SecurityContextHolder.getContext().getAuthentication(); +// if (auth instanceof MidpointAuthentication) { +// ((MidpointAuthentication) auth).setOverLockoutMaxAttempts(true); +// } +// recordModuleAuthenticationFailure(principal.getUsername(), principal, connEnv, getCredentialsPolicy(principal, authnCtx), "password locked-out"); +// throw new LockedException("web.security.provider.locked"); +// } +// +// // Password age +// checkPasswordValidityAndAge(connEnv, principal, getCredential(credentials), credentialsPolicy); +// +// return passwordMatches(connEnv, principal, getCredential(credentials), authnCtx); +// } + protected SecurityPolicyType resolveSecurityPolicy(PrismObject user) { SecurityPolicyType securityPolicy = runPrivileged(new Producer() { private static final long serialVersionUID = 1L; @@ -526,7 +576,7 @@ public SecurityPolicyType run() { OperationResult result = new OperationResult(OPERATION_GET_SECURITY_POLICY); try { - return modelInteraction.getSecurityPolicy(user, task, result); + return modelInteraction.getSecurityPolicy(user, null, task, result); } catch (CommonException e) { LOGGER.error("Could not retrieve security policy: {}", e.getMessage(), e); return null; @@ -549,7 +599,7 @@ protected Task createAnonymousTask(String operation) { private void resolveReference(ObjectDelta delta, OperationResult result) { try { - ModelImplUtils.resolveReferences(delta, repositoryService, false, false, EvaluationTimeType.IMPORT, true, prismContext, result); + ModelImplUtils.resolveReferences(delta, repositoryService, false, false, EvaluationTimeType.IMPORT, true, result); } catch (SystemException e) { StatusRuntimeException exception = Status.INVALID_ARGUMENT .withDescription("invalid_reference") @@ -641,7 +691,7 @@ public void requestAssignments(RequestAssignmentsRequest request, StreamObserver TaskType reqTask = createSingleRecurrenceTask(loggedInUser, "Request assignments - " + UUID.randomUUID().toString(), UserType.COMPLEX_TYPE, - query, delta, createOptions(request.getComment()), TaskCategory.EXECUTE_CHANGES); + query, delta, createOptions(request.getComment()), "ExecuteChanges"); // TaskCategory.EXECUTE_CHANGES); taskOid = runTask(reqTask, task, parentResult); } @@ -735,7 +785,7 @@ private ModelExecuteOptions createOptions(String comment) { businessContextType = null; } // ModelExecuteOptions options = ExecuteChangeOptionsDto.createFromSystemConfiguration().createOptions(); - ModelExecuteOptions options = ModelExecuteOptions.fromRestOptions(Collections.EMPTY_LIST, prismContext); + ModelExecuteOptions options = ModelExecuteOptions.fromRestOptions(Collections.EMPTY_LIST); if (options == null) { options = new ModelExecuteOptions(prismContext); } @@ -751,14 +801,14 @@ public void addUser(AddUserRequest request, StreamObserver resp Task task = ctx.task; OperationResult parentResult = task.getResult().createSubresult(OPERATION_ADD_USER); - ModelExecuteOptions modelExecuteOptions = ModelExecuteOptions.fromRestOptions(request.getOptionsList(), prismContext); + ModelExecuteOptions modelExecuteOptions = ModelExecuteOptions.fromRestOptions(request.getOptionsList()); UserTypeMessage message = request.getProfile(); PrismObject user = toPrismObject(prismContext, repositoryService, message); // To handle error to resolve reference, call resolveReferences here. try { - ModelImplUtils.resolveReferences(user, repositoryService, false, false, EvaluationTimeType.IMPORT, true, prismContext, parentResult); + ModelImplUtils.resolveReferences(user, repositoryService, false, false, EvaluationTimeType.IMPORT, true, parentResult); } catch (SystemException e) { StatusRuntimeException exception = Status.INVALID_ARGUMENT .withDescription("invalid_reference") @@ -795,7 +845,7 @@ public void modifyUser(ModifyUserRequest request, StreamObserver objectDelta = prismContext.deltaFactory().object().createModifyDelta(userOid, delta, UserType.class); // delta for nonce - NonceType nonce = user.getCredentials().getNonce(); - if (clearNonce && nonce != null) { - objectDelta.addModificationDeleteContainer(ItemPath.create(UserType.F_CREDENTIALS, CredentialsType.F_NONCE), - nonce.clone()); + if (clearNonce) { + CredentialsType credentials = user.getCredentials(); + if (credentials != null) { + NonceType nonce = credentials.getNonce(); + if (nonce != null) { + objectDelta.addModificationDeleteContainer(ItemPath.create(UserType.F_CREDENTIALS, CredentialsType.F_NONCE), + nonce.clone()); + } + } } // delta for lifecycleState @@ -949,14 +1004,14 @@ public void addRole(AddRoleRequest request, StreamObserver re Task task = ctx.task; OperationResult parentResult = task.getResult().createSubresult(OPERATION_ADD_ROLE); - ModelExecuteOptions modelExecuteOptions = ModelExecuteOptions.fromRestOptions(request.getOptionsList(), prismContext); + ModelExecuteOptions modelExecuteOptions = ModelExecuteOptions.fromRestOptions(request.getOptionsList()); RoleTypeMessage message = request.getObject(); PrismObject object = toPrismObject(prismContext, repositoryService, message); // To handle error to resolve reference, call resolveReferences here. try { - ModelImplUtils.resolveReferences(object, repositoryService, false, false, EvaluationTimeType.IMPORT, true, prismContext, parentResult); + ModelImplUtils.resolveReferences(object, repositoryService, false, false, EvaluationTimeType.IMPORT, true, parentResult); } catch (SystemException e) { StatusRuntimeException exception = Status.INVALID_ARGUMENT .withDescription("invalid_reference") @@ -993,7 +1048,7 @@ public void getRole(GetRoleRequest request, StreamObserver resp OperationResult parentResult = task.getResult().createSubresult(OPERATION_GET_ROLE); - String oid = resolveOid(UserType.class, request.getOid(), request.getName(), task, parentResult); + String oid = resolveOid(RoleType.class, request.getOid(), request.getName(), task, parentResult); List options = request.getOptionsList(); List include = request.getIncludeList(); @@ -1026,14 +1081,14 @@ public void addOrg(AddOrgRequest request, StreamObserver resp Task task = ctx.task; OperationResult parentResult = task.getResult().createSubresult(OPERATION_ADD_ORG); - ModelExecuteOptions modelExecuteOptions = ModelExecuteOptions.fromRestOptions(request.getOptionsList(), prismContext); + ModelExecuteOptions modelExecuteOptions = ModelExecuteOptions.fromRestOptions(request.getOptionsList()); OrgTypeMessage message = request.getObject(); PrismObject object = toPrismObject(prismContext, repositoryService, message); // To handle error to resolve reference, call resolveReferences here. try { - ModelImplUtils.resolveReferences(object, repositoryService, false, false, EvaluationTimeType.IMPORT, true, prismContext, parentResult); + ModelImplUtils.resolveReferences(object, repositoryService, false, false, EvaluationTimeType.IMPORT, true, parentResult); } catch (SystemException e) { StatusRuntimeException exception = Status.INVALID_ARGUMENT .withDescription("invalid_reference") @@ -1070,7 +1125,7 @@ public void getOrg(GetOrgRequest request, StreamObserver respons OperationResult parentResult = task.getResult().createSubresult(OPERATION_GET_ORG); - String oid = resolveOid(UserType.class, request.getOid(), request.getName(), task, parentResult); + String oid = resolveOid(OrgType.class, request.getOid(), request.getName(), task, parentResult); List options = request.getOptionsList(); List include = request.getIncludeList(); @@ -1103,14 +1158,14 @@ public void addService(AddServiceRequest request, StreamObserver object = toPrismObject(prismContext, repositoryService, message); // To handle error to resolve reference, call resolveReferences here. try { - ModelImplUtils.resolveReferences(object, repositoryService, false, false, EvaluationTimeType.IMPORT, true, prismContext, parentResult); + ModelImplUtils.resolveReferences(object, repositoryService, false, false, EvaluationTimeType.IMPORT, true, parentResult); } catch (SystemException e) { StatusRuntimeException exception = Status.INVALID_ARGUMENT .withDescription("invalid_reference") @@ -1147,7 +1202,7 @@ public void getService(GetServiceRequest request, StreamObserver options = request.getOptionsList(); List include = request.getIncludeList(); @@ -1634,7 +1689,7 @@ public void addObject(AddObjectRequest request, StreamObserver clazz; if (request.hasType()) { @@ -1650,7 +1705,7 @@ public void addObject(AddObjectRequest request, StreamObserver options = request.getOptionsList(); @@ -1745,7 +1800,7 @@ public void deleteObject(DeleteObjectRequest request, StreamObserver emptyDelta = prismContext.deltaFactory().object() @@ -1870,7 +1925,7 @@ public void getLookupTable(GetLookupTableRequest request, StreamObserver> queryOptions = SelectorOptions.createCollection(prismContext.toUniformPath(LookupTableType.F_ROW), GetOperationOptions.createRetrieve(query)); - getOptions = GetOperationOptions.merge(prismContext, getOptions, queryOptions); + getOptions = GetOperationOptions.merge(getOptions, queryOptions); } PrismObject lookupTable = modelCrudService.getObject(LookupTableType.class, oid, getOptions, task, parentResult); diff --git a/self-services/src/main/java/jp/openstandia/midpoint/grpc/TypeConverter.java b/self-services/src/main/java/jp/openstandia/midpoint/grpc/TypeConverter.java index 754d3e2..e034c87 100644 --- a/self-services/src/main/java/jp/openstandia/midpoint/grpc/TypeConverter.java +++ b/self-services/src/main/java/jp/openstandia/midpoint/grpc/TypeConverter.java @@ -393,7 +393,7 @@ private static Class toObjectClass(QName type) { public static ObjectQuery toObjectQuery(PrismContext prismContext, Class queryClass, QueryMessage message) { S_FilterEntryOrEmpty builder = QueryBuilder.queryFor(queryClass, prismContext); - S_AtomicFilterExit exitFilter = toObjectFilter(prismContext, builder, message.getFilter()); + S_FilterExit exitFilter = toObjectFilter(prismContext, builder, message.getFilter()); S_QueryExit exitBuilder; if (exitFilter != null) { @@ -458,7 +458,7 @@ private static OrderDirection toOrderDirectionValue(OrderDirectionType message) return OrderDirection.ASCENDING; } - public static S_AtomicFilterExit toObjectFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, ObjectFilterMessage message) { + public static S_FilterExit toObjectFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, ObjectFilterMessage message) { if (message.hasAnd()) { return toAndQuery(prismContext, builder, message.getAnd()); } else if (message.hasOr()) { @@ -499,7 +499,7 @@ public static S_AtomicFilterExit toObjectFilter(PrismContext prismContext, S_Fil return null; } - public static S_AtomicFilterExit toObjectFilter(PrismContext prismContext, S_FilterEntry builder, ObjectFilterMessage message) { + public static S_FilterExit toObjectFilter(PrismContext prismContext, S_FilterEntry builder, ObjectFilterMessage message) { if (message.hasAnd()) { return toAndQuery(prismContext, builder, message.getAnd()); } else if (message.hasOr()) { @@ -530,7 +530,7 @@ public static ItemPath toItemPath(String message) { }).collect(Collectors.toList())); } - public static S_AtomicFilterExit toEqFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + public static S_FilterExit toEqFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .eq(message.getValue()); @@ -540,7 +540,7 @@ public static S_AtomicFilterExit toEqFilter(S_FilterEntryOrEmpty builder, Filter return filter; } - public static S_AtomicFilterExit toStartsWithFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + public static S_FilterExit toStartsWithFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .startsWith(message.getValue()); @@ -550,7 +550,7 @@ public static S_AtomicFilterExit toStartsWithFilter(S_FilterEntryOrEmpty builder return filter; } - private static S_AtomicFilterExit toContainsFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + private static S_FilterExit toContainsFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .contains(message.getValue()); @@ -560,7 +560,7 @@ private static S_AtomicFilterExit toContainsFilter(S_FilterEntryOrEmpty builder, return filter; } - public static S_AtomicFilterExit toEndsWithFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + public static S_FilterExit toEndsWithFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .endsWith(message.getValue()); @@ -570,7 +570,7 @@ public static S_AtomicFilterExit toEndsWithFilter(S_FilterEntryOrEmpty builder, return filter; } - public static S_AtomicFilterExit toEqPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + public static S_FilterExit toEqPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .eqPoly(message.getValue()); @@ -580,7 +580,7 @@ public static S_AtomicFilterExit toEqPolyFilter(S_FilterEntryOrEmpty builder, Fi return filter; } - public static S_AtomicFilterExit toStartsWithPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + public static S_FilterExit toStartsWithPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .startsWithPoly(message.getValue()); @@ -590,7 +590,7 @@ public static S_AtomicFilterExit toStartsWithPolyFilter(S_FilterEntryOrEmpty bui return filter; } - private static S_AtomicFilterExit toContainsPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + private static S_FilterExit toContainsPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .containsPoly(message.getValue()); @@ -600,7 +600,7 @@ private static S_AtomicFilterExit toContainsPolyFilter(S_FilterEntryOrEmpty buil return filter; } - public static S_AtomicFilterExit toEndsWithPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { + public static S_FilterExit toEndsWithPolyFilter(S_FilterEntryOrEmpty builder, FilterEntryMessage message) { S_MatchingRuleEntry filter = builder.item(toItemPath(message)) .endsWithPoly(message.getValue()); @@ -610,12 +610,12 @@ public static S_AtomicFilterExit toEndsWithPolyFilter(S_FilterEntryOrEmpty build return filter; } - public static S_AtomicFilterExit toAndQuery(PrismContext prismContext, S_FilterEntry builder, AndFilterMessage message) { + public static S_FilterExit toAndQuery(PrismContext prismContext, S_FilterEntry builder, AndFilterMessage message) { List list = message.getConditionsList(); for (int i = 0; i < list.size(); i++) { ObjectFilterMessage filter = list.get(i); - S_AtomicFilterExit f = toObjectFilter(prismContext, builder, filter); + S_FilterExit f = toObjectFilter(prismContext, builder, filter); if (i < list.size() - 1) { builder = f.and(); @@ -626,12 +626,12 @@ public static S_AtomicFilterExit toAndQuery(PrismContext prismContext, S_FilterE return null; } - public static S_AtomicFilterExit toOrQuery(PrismContext prismContext, S_FilterEntry builder, OrFilterMessage message) { + public static S_FilterExit toOrQuery(PrismContext prismContext, S_FilterEntry builder, OrFilterMessage message) { List list = message.getConditionsList(); for (int i = 0; i < list.size(); i++) { ObjectFilterMessage filter = list.get(i); - S_AtomicFilterExit f = toObjectFilter(prismContext, builder, filter); + S_FilterExit f = toObjectFilter(prismContext, builder, filter); if (i < list.size() - 1) { builder = f.or(); @@ -642,10 +642,10 @@ public static S_AtomicFilterExit toOrQuery(PrismContext prismContext, S_FilterEn return null; } - public static S_AtomicFilterExit toNotQuery(PrismContext prismContext, S_FilterEntryOrEmpty builder, NotFilterMessage message) { - S_AtomicFilterEntry not = builder.not(); + public static S_FilterExit toNotQuery(PrismContext prismContext, S_FilterEntryOrEmpty builder, NotFilterMessage message) { + S_FilterEntry not = builder.not(); S_FilterEntryOrEmpty block = not.block(); - S_AtomicFilterExit q = toObjectFilter(prismContext, block, message.getFilter()); + S_FilterExit q = toObjectFilter(prismContext, block, message.getFilter()); return q.endBlock(); } @@ -691,17 +691,17 @@ public static SearchFilterType toEqFilter(PrismContext prismContext, QName type, } } - public static S_AtomicFilterExit toInOidFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterInOidMessage message) { + public static S_FilterExit toInOidFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterInOidMessage message) { return builder.id(message.getValueList().toArray(new String[0])); } - public static S_AtomicFilterExit toRefFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterReferenceMessage message) { + public static S_FilterExit toRefFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterReferenceMessage message) { ObjectReferenceType ref = toObjectReferenceTypeValue(prismContext, message.getValue()); return builder.item(toItemPath(message)) .ref(ref.asReferenceValue()); } - public static S_AtomicFilterExit toOrgRootFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterOrgRootMessage message) { + public static S_FilterExit toOrgRootFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterOrgRootMessage message) { if (message.getIsRoot()) { return builder.isRoot(); } @@ -710,7 +710,7 @@ public static S_AtomicFilterExit toOrgRootFilter(PrismContext prismContext, S_Fi return builder.undefined(); } - public static S_AtomicFilterExit toOrgRefFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterOrgReferenceMessage message) { + public static S_FilterExit toOrgRefFilter(PrismContext prismContext, S_FilterEntryOrEmpty builder, FilterOrgReferenceMessage message) { ObjectReferenceType ref = toObjectReferenceTypeValue(prismContext, message.getValue()); return builder.isInScopeOf(ref.asReferenceValue(), toOrgFilterScope(message.getScope())); } @@ -986,7 +986,7 @@ public static PrismObject toPrismObject(PrismContext prismContext, Rep // ObjectType user.setName(toPolyStringTypeValue(message.getName())); user.setDescription(toStringValue(message.getDescription())); - user.createSubtypeList().addAll(message.getSubtypeList()); + user.getSubtype().addAll(message.getSubtypeList()); user.setLifecycleState(toStringValue(message.getLifecycleState())); // AssignmentHolderType @@ -1013,8 +1013,8 @@ public static PrismObject toPrismObject(PrismContext prismContext, Rep user.setHonorificSuffix(toPolyStringTypeValue(message.getHonorificSuffix())); user.setTitle(toPolyStringTypeValue(message.getTitle())); user.setEmployeeNumber(toStringValue(message.getEmployeeNumber())); - user.createOrganizationList().addAll(toPolyStringTypeValueList(message.getOrganizationList())); - user.createOrganizationalUnitList().addAll(toPolyStringTypeValueList(message.getOrganizationalUnitList())); + user.getOrganization().addAll(toPolyStringTypeValueList(message.getOrganizationList())); + user.getOrganizationalUnit().addAll(toPolyStringTypeValueList(message.getOrganizationalUnitList())); // Extension addExtensionType(prismContext, user, message.getExtensionMap()); @@ -1028,7 +1028,7 @@ public static PrismObject toPrismObject(PrismContext prismContext, Rep // ObjectType object.setName(toPolyStringTypeValue(message.getName())); object.setDescription(toStringValue(message.getDescription())); - object.createSubtypeList().addAll(message.getSubtypeList()); + object.getSubtype().addAll(message.getSubtypeList()); object.setLifecycleState(toStringValue(message.getLifecycleState())); // AssignmentHolderType @@ -1067,7 +1067,7 @@ public static PrismObject toPrismObject(PrismContext prismContext, Repo // ObjectType object.setName(toPolyStringTypeValue(message.getName())); object.setDescription(toStringValue(message.getDescription())); - object.createSubtypeList().addAll(message.getSubtypeList()); + object.getSubtype().addAll(message.getSubtypeList()); object.setLifecycleState(toStringValue(message.getLifecycleState())); // AssignmentHolderType @@ -1094,7 +1094,7 @@ public static PrismObject toPrismObject(PrismContext prismContext, Repo // OrgType // object.createOrgTypeList().addAll(message.getOrgTypeList()); object.setTenant(toBooleanValue(message.getTenant())); - object.createMailDomainList().addAll(message.getMailDomainList()); + object.getSubtype().addAll(message.getMailDomainList()); object.setDisplayOrder(toIntValue(message.getDisplayOrder())); // Extension @@ -1109,7 +1109,7 @@ public static PrismObject toPrismObject(PrismContext prismContext, // ObjectType object.setName(toPolyStringTypeValue(message.getName())); object.setDescription(toStringValue(message.getDescription())); - object.createSubtypeList().addAll(message.getSubtypeList()); + object.getSubtype().addAll(message.getSubtypeList()); object.setLifecycleState(toStringValue(message.getLifecycleState())); // AssignmentHolderType @@ -1177,13 +1177,15 @@ public static PrismObject toPrismObject(PrismContext prismContext, Class clazz, throw exception; } + PrismObject prismObject = objectType.asPrismObject(); + if (values.size() > 0) { // Convert PrismContainerValueMessage to PrismObjectValue PrismObjectValue prismObjectValue = toPrismObjectValue(prismContext, definition, values.get(0), objectType); - instantiate.setValue(prismObjectValue); + prismObject.setValue(prismObjectValue); } - return objectType.asPrismObject(); + return prismObject; } private static List toPolyStringTypeValueList(List organizationList) { @@ -1488,7 +1490,7 @@ public static Map toItemMessageMap(ExtensionType extension, // Currently, it doesn't use namespaceURI as the key String key = definition.getItemName().getLocalPart(); - if (!SelectorOptions.hasToLoadPath(item.getPath(), options, !hasInclude)) { + if (!SelectorOptions.hasToIncludePath(item.getPath(), options, !hasInclude)) { continue; } @@ -1517,7 +1519,7 @@ private static PrismContainerValueMessage toPrismContainerValueMessage(PrismCont } for (Item item : value.getItems()) { - if (!SelectorOptions.hasToLoadPath(item.getPath(), options, !hasInclude)) { + if (!SelectorOptions.hasToIncludePath(item.getPath(), options, !hasInclude)) { continue; } @@ -1547,7 +1549,7 @@ public static PrismObjectMessage toPrismObjectMessage(ItemDefinition definition, .setOid(prismObject.getOid()); for (Item item : prismObject.getValue().getItems()) { - if (!SelectorOptions.hasToLoadPath(item.getPath(), options, !hasInclude)) { + if (!SelectorOptions.hasToIncludePath(item.getPath(), options, !hasInclude)) { continue; } builder.putValue(item.getElementName().getLocalPart(), toItemMessage(item.getDefinition(), item, options, hasInclude)); diff --git a/self-services/src/test/java/TestJWTAuthClient.java b/self-services/src/test/java/TestJWTAuthClient.java deleted file mode 100644 index 0b854a2..0000000 --- a/self-services/src/test/java/TestJWTAuthClient.java +++ /dev/null @@ -1,72 +0,0 @@ -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; -import io.grpc.Metadata; -import io.grpc.stub.MetadataUtils; -import jp.openstandia.midpoint.grpc.*; -import org.springframework.boot.json.BasicJsonParser; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.io.UnsupportedEncodingException; -import java.util.Map; - -import static org.springframework.http.HttpMethod.POST; - -public class TestJWTAuthClient { - - public static void main(String[] args) throws UnsupportedEncodingException { - ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 6565) - .usePlaintext() - .build(); - - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = clientAuthenticate("midpoint-grpc-client", "31cc0ded-3155-4a63-8ee3-415698789672", - "http://localhost:18080/auth/realms/master/protocol/openid-connect/token"); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Bearer " + token); - headers.put(Constant.SwitchToPrincipalByNameMetadataKey, "test"); - - stub = MetadataUtils.attachHeaders(stub, headers); - - ModifyProfileRequest request = ModifyProfileRequest.newBuilder() - .addModifications( - UserItemDeltaMessage.newBuilder() - .setUserTypePath(DefaultUserTypePath.F_FAMILY_NAME) - .addValuesToReplace("Bar") - .build() - ) - .build(); - - stub.modifyProfile(request); - } - - public static String clientAuthenticate(String clientId, String clientSecret, String tokenEndpoint) { - // TODO Use client_jwt authentication for security - RestTemplate tokenTemplate = new RestTemplate(); - - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("client_id", clientId); - body.add("client_secret", clientSecret); - body.add("grant_type", "client_credentials"); - - HttpHeaders headers = new HttpHeaders(); - headers.add("Accept", "application/x-www-form-urlencoded"); - - HttpEntity entity = new HttpEntity<>(body, headers); - - ResponseEntity res = tokenTemplate.exchange( - tokenEndpoint, POST, entity, String.class); - - Map map = new BasicJsonParser().parseMap(res.getBody()); - - System.out.println("Got access token for midpoint-grpc."); - - return (String) map.get("access_token"); - } -} diff --git a/self-services/src/test/java/jp/openstandia/midpoint/grpc/SelfServiceResourceITest.java b/self-services/src/test/java/jp/openstandia/midpoint/grpc/SelfServiceResourceITest.java index 25822f2..4069de5 100644 --- a/self-services/src/test/java/jp/openstandia/midpoint/grpc/SelfServiceResourceITest.java +++ b/self-services/src/test/java/jp/openstandia/midpoint/grpc/SelfServiceResourceITest.java @@ -3,11 +3,11 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import io.grpc.*; import io.grpc.stub.MetadataUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; +import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Base64; import java.util.List; @@ -15,20 +15,52 @@ @ExtendWith(MidPointGrpcTestRunner.class) class SelfServiceResourceITest { - static ManagedChannel channel; + private static ManagedChannel channel; + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub defaultUserStub; + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub defaultServiceAccountStub; + + private static final String GRPC_SERVICE_ACCOUNT_NAME = "grpc-service-account"; + private static final String GRPC_SERVICE_ROLE_NAME = "grpc-service-role"; + private static String GRPC_SERVICE_ROLE_OID = null; + private static final String DEFAULT_TEST_USERNAME = "defaultUser001"; @BeforeAll static void init() { channel = ManagedChannelBuilder.forAddress("localhost", 6565) .usePlaintext() .build(); + + // Add gRPC service account and role + addGrpcServiceAccount(GRPC_SERVICE_ACCOUNT_NAME, "password"); + defaultUserStub = newStubWithSwitchUserByUsername(GRPC_SERVICE_ACCOUNT_NAME, "password", DEFAULT_TEST_USERNAME, true); + defaultServiceAccountStub = newStub(GRPC_SERVICE_ACCOUNT_NAME, "password", true); } @AfterAll static void cleanup() { + // Cleanup + // Delete gRPC service account and role + deleteObject(DefaultObjectType.USER_TYPE, GRPC_SERVICE_ACCOUNT_NAME); + deleteObject(DefaultObjectType.ROLE_TYPE, GRPC_SERVICE_ROLE_NAME); + channel.shutdownNow(); } + @BeforeEach + void beforeMethod() { + // Add default test user + addUser(DEFAULT_TEST_USERNAME, "DefaultUser001", + "00000000-0000-0000-0000-000000000008" // End User + ); + } + + @AfterEach + void afterMethod() { + // Cleanup + // Delete default test user + deleteObject(DefaultObjectType.USER_TYPE, DEFAULT_TEST_USERNAME); + } + @Test void unauthenticated() throws Exception { SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); @@ -243,36 +275,18 @@ void switchUserWithArchivedUser() throws Exception { @Test void getSelf() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - GetSelfRequest request = GetSelfRequest.newBuilder() .build(); - GetSelfResponse response = stub.getSelf(request); + GetSelfResponse response = defaultUserStub.getSelf(request); UserTypeMessage user = response.getProfile(); - assertEquals("Administrator", user.getFamilyName().getOrig()); - assertEquals("administrator", user.getFamilyName().getNorm()); + assertEquals("DefaultUser001", user.getFamilyName().getOrig()); + assertEquals("defaultuser001", user.getFamilyName().getNorm()); } @Test void modifyProfile() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - ModifyProfileRequest request = ModifyProfileRequest.newBuilder() .addModifications( UserItemDeltaMessage.newBuilder() @@ -282,58 +296,38 @@ void modifyProfile() throws Exception { ) .build(); - stub.modifyProfile(request); + defaultUserStub.modifyProfile(request); + + assertEquals("Foo", defaultUserStub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getAdditionalName().getOrig()); } @Test void updateCredential() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - UpdateCredentialRequest request = UpdateCredentialRequest.newBuilder() - .setOld("5ecr3t") - .setNew("password") + .setOld("password") + .setNew("newpassword") .build(); - stub.updateCredential(request); + defaultUserStub.updateCredential(request); - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:password".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); - - // Back to original password by forceUpdateCredential - ForceUpdateCredentialRequest forceReq = ForceUpdateCredentialRequest.newBuilder() - .setNew("5ecr3t") + // Back to original password + request = UpdateCredentialRequest.newBuilder() + .setOld("newpassword") + .setNew("password") .build(); - stub.forceUpdateCredential(forceReq); + defaultUserStub.updateCredential(request); } @Test void passwordPolicyErrorWithSingleError() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - UpdateCredentialRequest request = UpdateCredentialRequest.newBuilder() - .setOld("5ecr3t") + .setOld("password") .setNew("123") .build(); try { - stub.updateCredential(request); + defaultUserStub.updateCredential(request); fail("Should be thrown Exception of password policy error"); } catch (StatusRuntimeException e) { assertEquals(Status.Code.INVALID_ARGUMENT, e.getStatus().getCode()); @@ -374,22 +368,13 @@ void passwordPolicyErrorWithSingleError() throws Exception { @Test void passwordPolicyErrorWithMultipleError() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - UpdateCredentialRequest request = UpdateCredentialRequest.newBuilder() - .setOld("5ecr3t") + .setOld("password") .setNew("1111") .build(); try { - stub.updateCredential(request); + defaultUserStub.updateCredential(request); fail("Should be thrown Exception of password policy error"); } catch (StatusRuntimeException e) { assertEquals(Status.Code.INVALID_ARGUMENT, e.getStatus().getCode()); @@ -432,70 +417,43 @@ void passwordPolicyErrorWithMultipleError() throws Exception { @Test void forceUpdateCredential() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // Force update password ForceUpdateCredentialRequest request = ForceUpdateCredentialRequest.newBuilder() - .setNew("password") + .setNew("newpassword") .build(); - stub.forceUpdateCredential(request); + defaultUserStub.forceUpdateCredential(request); // Check the password was changed try { - stub.getSelf(GetSelfRequest.newBuilder().build()); + UpdateCredentialRequest updateCredentialRequest = UpdateCredentialRequest.newBuilder() + .setOld("password") + .setNew("foobar") + .build(); + defaultUserStub.updateCredential(updateCredentialRequest); fail("Password wasn't changed"); } catch (StatusRuntimeException e) { - assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); + assertEquals(Status.INVALID_ARGUMENT.getCode(), e.getStatus().getCode()); + assertEquals("invalid_credential", e.getStatus().getDescription()); } - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:password".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); - - // Back to original password by forceUpdateCredential - ForceUpdateCredentialRequest forceReq = ForceUpdateCredentialRequest.newBuilder() - .setNew("5ecr3t") + // Force update password again + request = ForceUpdateCredentialRequest.newBuilder() + .setNew("newpassword2") .build(); - stub.forceUpdateCredential(forceReq); + defaultUserStub.forceUpdateCredential(request); // Check the password was changed - try { - stub.getSelf(GetSelfRequest.newBuilder().build()); - fail("Password wasn't changed"); - } catch (StatusRuntimeException e) { - assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); - } - - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); - - // Check auth with new password - stub.getSelf(GetSelfRequest.newBuilder().build()); + UpdateCredentialRequest updateCredentialRequest = UpdateCredentialRequest.newBuilder() + .setOld("newpassword2") + .setNew("password") + .build(); + defaultUserStub.updateCredential(updateCredentialRequest); } @Test void forceUpdateCredentialWithNonceClear() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // Save nonce to the user ModifyProfileRequest modifyProfileRequest = ModifyProfileRequest.newBuilder() .addModifications( @@ -506,34 +464,34 @@ void forceUpdateCredentialWithNonceClear() throws Exception { ) .build(); - stub.modifyProfile(modifyProfileRequest); + defaultUserStub.modifyProfile(modifyProfileRequest); // Force update password ForceUpdateCredentialRequest request = ForceUpdateCredentialRequest.newBuilder() - .setNew("password") + .setNew("newpassword") .build(); - stub.forceUpdateCredential(request); + defaultUserStub.forceUpdateCredential(request); // Check the password was changed try { - stub.getSelf(GetSelfRequest.newBuilder().build()); + UpdateCredentialRequest updateCredentialRequest = UpdateCredentialRequest.newBuilder() + .setOld("password") + .setNew("newpassword") + .build(); + defaultUserStub.updateCredential(updateCredentialRequest); fail("Password wasn't changed"); } catch (StatusRuntimeException e) { - assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); + assertEquals(Status.INVALID_ARGUMENT.getCode(), e.getStatus().getCode()); + assertEquals("invalid_credential", e.getStatus().getDescription()); } - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:password".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); - // Check nonce isn't cleared yet CheckNonceRequest checkNonceRequest = CheckNonceRequest.newBuilder() .setNonce("123456") .build(); - CheckNonceResponse checkNonceResponse = stub.checkNonce(checkNonceRequest); + CheckNonceResponse checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -542,27 +500,27 @@ void forceUpdateCredentialWithNonceClear() throws Exception { // Back to original password with clear nonce and active ForceUpdateCredentialRequest forceReq = ForceUpdateCredentialRequest.newBuilder() - .setNew("5ecr3t") + .setNew("password") .setClearNonce(true) .build(); - stub.forceUpdateCredential(forceReq); + defaultUserStub.forceUpdateCredential(forceReq); // Check the password was changed try { - stub.getSelf(GetSelfRequest.newBuilder().build()); + UpdateCredentialRequest updateCredentialRequest = UpdateCredentialRequest.newBuilder() + .setOld("newpassword") + .setNew("password") + .build(); + defaultUserStub.updateCredential(updateCredentialRequest); fail("Password wasn't changed"); } catch (StatusRuntimeException e) { - assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); + assertEquals(Status.INVALID_ARGUMENT.getCode(), e.getStatus().getCode()); + assertEquals("invalid_credential", e.getStatus().getDescription()); } - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); - // Check nonce was cleared - checkNonceResponse = stub.checkNonce(checkNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -571,18 +529,43 @@ void forceUpdateCredentialWithNonceClear() throws Exception { } @Test - void forceUpdateCredentialWithNonceClearAndActive() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); + void forceUpdateCredentialWithNonceClearForNoCredentialsUser() throws Exception { + // Add test user without credentials + AddUserRequest addUserRequest = AddUserRequest.newBuilder() + .setProfile(UserTypeMessage.newBuilder() + .setName(PolyStringMessage.newBuilder().setOrig("user001_no_cred")) + ) + .build(); - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); + AddUserResponse response = defaultUserStub.addUser(addUserRequest); - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); + assertNotNull(response.getOid()); - stub = MetadataUtils.attachHeaders(stub, headers); + // Switch to the created user + SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = newDefaultStubWithSwitchUserByOid("user001_no_cred", true); - // Save nonce to the user - ModifyProfileRequest modifyProfileRequest = ModifyProfileRequest.newBuilder() + // Force update password with nonce clear + ForceUpdateCredentialRequest request = ForceUpdateCredentialRequest.newBuilder() + .setNew("password") + .setClearNonce(true) + .build(); + + stub.forceUpdateCredential(request); + + // Cleanup + + // Delete the test user + defaultUserStub.deleteObject(DeleteObjectRequest.newBuilder() + .setOid(response.getOid()) + .setObjectType(DefaultObjectType.USER_TYPE) + .build()); + } + + @Test + void forceUpdateCredentialWithNonceClearAndActiveByServiceAccount() throws Exception { + // Save nonce to the default user + ModifyUserRequest modifyUserRequest = ModifyUserRequest.newBuilder() + .setName(DEFAULT_TEST_USERNAME) .addModifications( UserItemDeltaMessage.newBuilder() .setPath("credentials/nonce/value") @@ -591,34 +574,14 @@ void forceUpdateCredentialWithNonceClearAndActive() throws Exception { ) .build(); - stub.modifyProfile(modifyProfileRequest); - - // Force update password - ForceUpdateCredentialRequest request = ForceUpdateCredentialRequest.newBuilder() - .setNew("password") - .build(); - - stub.forceUpdateCredential(request); - - // Check the password was changed - try { - stub.getSelf(GetSelfRequest.newBuilder().build()); - fail("Password wasn't changed"); - } catch (StatusRuntimeException e) { - assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); - } - - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:password".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); + defaultUserStub.modifyUser(modifyUserRequest); - // Check nonce isn't cleared yet + // Check nonce was saved CheckNonceRequest checkNonceRequest = CheckNonceRequest.newBuilder() .setNonce("123456") .build(); - CheckNonceResponse checkNonceResponse = stub.checkNonce(checkNonceRequest); + CheckNonceResponse checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -626,32 +589,32 @@ void forceUpdateCredentialWithNonceClearAndActive() throws Exception { assertTrue(checkNonceResponse.getError().isEmpty()); // Check lifecycleState isn't active yet - assertEquals("", stub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); + assertEquals("", defaultUserStub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); - // Back to original password with clear nonce and active + // Force update password with nonce clear and activation ForceUpdateCredentialRequest forceReq = ForceUpdateCredentialRequest.newBuilder() - .setNew("5ecr3t") + .setNew("newpassword") .setClearNonce(true) .setActive(true) .build(); - stub.forceUpdateCredential(forceReq); + defaultUserStub.forceUpdateCredential(forceReq); // Check the password was changed try { - stub.getSelf(GetSelfRequest.newBuilder().build()); + UpdateCredentialRequest updateCredentialRequest = UpdateCredentialRequest.newBuilder() + .setOld("password") + .setNew("newpassword") + .build(); + defaultUserStub.updateCredential(updateCredentialRequest); fail("Password wasn't changed"); } catch (StatusRuntimeException e) { - assertEquals(Status.UNAUTHENTICATED.getCode(), e.getStatus().getCode()); + assertEquals(Status.INVALID_ARGUMENT.getCode(), e.getStatus().getCode()); + assertEquals("invalid_credential", e.getStatus().getDescription()); } - // Update authorization header with new password - token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - stub = MetadataUtils.attachHeaders(stub, headers); - // Check nonce was cleared - checkNonceResponse = stub.checkNonce(checkNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -659,33 +622,16 @@ void forceUpdateCredentialWithNonceClearAndActive() throws Exception { assertEquals("not_found", checkNonceResponse.getError()); // Check lifecycleState was active - assertEquals(SchemaConstants.LIFECYCLE_ACTIVE, stub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); - - // Clear lifecycleState - ModifyProfileRequest profileRequest = ModifyProfileRequest.newBuilder() - .addModifications(UserItemDeltaMessage.newBuilder() - .setUserTypePath(DefaultUserTypePath.F_LIFECYCLE_STATE) - .addValuesToDelete(SchemaConstants.LIFECYCLE_ACTIVE)) - .build(); - stub.modifyProfile(profileRequest); + assertEquals(SchemaConstants.LIFECYCLE_ACTIVE, defaultUserStub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); } @Test void generateNonce() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - GenerateValueRequest request = GenerateValueRequest.newBuilder() .setValuePolicyOid("00000000-0000-0000-0000-000000000003") .build(); - GenerateValueResponse response = stub.generateValue(request); + GenerateValueResponse response = defaultServiceAccountStub.generateValue(request); System.out.println("Generated nonce: " + response.getValue()); @@ -695,39 +641,25 @@ void generateNonce() throws Exception { @Test void checkNonce() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - - // Add - AddUserRequest request = AddUserRequest.newBuilder() - .setProfile(UserTypeMessage.newBuilder() - .setName(PolyStringMessage.newBuilder().setOrig("user001")) - .setEmployeeNumber("emp001") - .setLifecycleState("proposed") + // Save lifecycle to the default user + ModifyUserRequest modifyUserRequest = ModifyUserRequest.newBuilder() + .setName(DEFAULT_TEST_USERNAME) + .addModifications( + UserItemDeltaMessage.newBuilder() + .setPath("lifecycleState") + .addValuesToReplace("proposed") + .build() ) .build(); - AddUserResponse response = stub.addUser(request); - - assertNotNull(response.getOid()); - - // Switch to the created user - headers.put(Constant.SwitchToPrincipalMetadataKey, response.getOid()); - headers.put(Constant.RunPrivilegedMetadataKey, "true"); - stub = MetadataUtils.attachHeaders(stub, headers); + defaultUserStub.modifyUser(modifyUserRequest); // Check nonce when the user doesn't have CheckNonceRequest checkNonceRequest = CheckNonceRequest.newBuilder() .setNonce("123456") .build(); - CheckNonceResponse checkNonceResponse = stub.checkNonce(checkNonceRequest); + CheckNonceResponse checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -744,10 +676,10 @@ void checkNonce() throws Exception { ) .build(); - stub.modifyProfile(modifyProfileRequest); + defaultUserStub.modifyProfile(modifyProfileRequest); // Check nonce again - checkNonceResponse = stub.checkNonce(checkNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -758,7 +690,7 @@ void checkNonce() throws Exception { CheckNonceRequest invalidCheckNonceRequest = CheckNonceRequest.newBuilder() .setNonce("invalid") .build(); - checkNonceResponse = stub.checkNonce(invalidCheckNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(invalidCheckNonceRequest); System.out.println(checkNonceResponse); @@ -767,13 +699,13 @@ void checkNonce() throws Exception { // Update credential ForceUpdateCredentialRequest forceUpdateCredentialRequest = ForceUpdateCredentialRequest.newBuilder() - .setNew("5ecr3t") + .setNew("newpassword") .build(); - stub.forceUpdateCredential(forceUpdateCredentialRequest); + defaultUserStub.forceUpdateCredential(forceUpdateCredentialRequest); // Check nonce isn't cleared yet - checkNonceResponse = stub.checkNonce(checkNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -782,14 +714,14 @@ void checkNonce() throws Exception { // Update credential again UpdateCredentialRequest updateCredentialRequest = UpdateCredentialRequest.newBuilder() - .setOld("5ecr3t") - .setNew("P@ssw0rd") + .setOld("newpassword") + .setNew("newpassword2") .build(); - stub.updateCredential(updateCredentialRequest); + defaultUserStub.updateCredential(updateCredentialRequest); // Check nonce isn't cleared yet - checkNonceResponse = stub.checkNonce(checkNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -797,19 +729,19 @@ void checkNonce() throws Exception { assertTrue(checkNonceResponse.getError().isEmpty()); // Check lifecycleState isn't active yet - assertEquals(SchemaConstants.LIFECYCLE_PROPOSED, stub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); + assertEquals(SchemaConstants.LIFECYCLE_PROPOSED, defaultUserStub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); - // Update credential again with nonce clearing + // Update credential again with nonce clearing and activation ForceUpdateCredentialRequest forceUpdateCredentialRequestWithClearNonce = ForceUpdateCredentialRequest.newBuilder() - .setNew("5ecr3t") + .setNew("password") .setClearNonce(true) .setActive(true) .build(); - stub.forceUpdateCredential(forceUpdateCredentialRequestWithClearNonce); + defaultUserStub.forceUpdateCredential(forceUpdateCredentialRequestWithClearNonce); // Check nonce was cleared - checkNonceResponse = stub.checkNonce(checkNonceRequest); + checkNonceResponse = defaultUserStub.checkNonce(checkNonceRequest); System.out.println(checkNonceResponse); @@ -817,13 +749,7 @@ void checkNonce() throws Exception { assertEquals("not_found", checkNonceResponse.getError()); // Check lifecycleState was active - assertEquals(SchemaConstants.LIFECYCLE_ACTIVE, stub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); - - // Delete - stub.deleteObject(DeleteObjectRequest.newBuilder() - .setOid(response.getOid()) - .setObjectType(DefaultObjectType.USER_TYPE) - .build()); + assertEquals(SchemaConstants.LIFECYCLE_ACTIVE, defaultUserStub.getSelf(GetSelfRequest.newBuilder().build()).getProfile().getLifecycleState()); } @Test @@ -832,15 +758,6 @@ void requestRole() { @Test void user() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // Add AddUserRequest request = AddUserRequest.newBuilder() .setProfile(UserTypeMessage.newBuilder() @@ -849,7 +766,7 @@ void user() throws Exception { ) .build(); - AddUserResponse response = stub.addUser(request); + AddUserResponse response = defaultServiceAccountStub.addUser(request); assertNotNull(response.getOid()); @@ -858,13 +775,33 @@ void user() throws Exception { .setOid(response.getOid()) .build(); - GetUserResponse res2 = stub.getUser(req2); + GetUserResponse res2 = defaultServiceAccountStub.getUser(req2); assertEquals("user001", res2.getResult().getName().getOrig()); assertEquals("emp001", res2.getResult().getEmployeeNumber()); + // Modify by name + ModifyUserRequest modReq = ModifyUserRequest.newBuilder() + .setName("user001") + .addModifications(UserItemDeltaMessage.newBuilder() + .setUserTypePath(DefaultUserTypePath.F_NAME) + .addValuesToReplace("user001_mod") + ) + .build(); + + defaultServiceAccountStub.modifyUser(modReq); + + // Get by name + req2 = GetUserRequest.newBuilder() + .setName("user001_mod") + .build(); + + res2 = defaultServiceAccountStub.getUser(req2); + + assertEquals("emp001", res2.getResult().getEmployeeNumber()); + // Search - SearchUsersResponse res3 = stub.searchUsers(SearchRequest.newBuilder() + SearchUsersResponse res3 = defaultServiceAccountStub.searchUsers(SearchRequest.newBuilder() .setQuery(QueryMessage.newBuilder() .setFilter(ObjectFilterMessage.newBuilder() .setEq(FilterEntryMessage.newBuilder() @@ -875,8 +812,10 @@ void user() throws Exception { assertEquals(1, res3.getNumberOfAllResults()); assertEquals("emp001", res3.getResults(0).getEmployeeNumber()); + // Cleanup + // Delete - DeleteObjectResponse res4 = stub.deleteObject(DeleteObjectRequest.newBuilder() + DeleteObjectResponse res4 = defaultServiceAccountStub.deleteObject(DeleteObjectRequest.newBuilder() .setOid(response.getOid()) .setObjectType(DefaultObjectType.USER_TYPE) .build()); @@ -886,15 +825,6 @@ void user() throws Exception { @Test void role() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // Add AddRoleRequest request = AddRoleRequest.newBuilder() .setObject(RoleTypeMessage.newBuilder() @@ -903,7 +833,7 @@ void role() throws Exception { ) .build(); - AddObjectResponse response = stub.addRole(request); + AddObjectResponse response = defaultServiceAccountStub.addRole(request); assertNotNull(response.getOid()); @@ -912,13 +842,40 @@ void role() throws Exception { .setOid(response.getOid()) .build(); - GetRoleResponse res2 = stub.getRole(req2); + GetRoleResponse res2 = defaultServiceAccountStub.getRole(req2); assertEquals("role001", res2.getResult().getName().getOrig()); assertEquals("testRole", res2.getResult().getSubtype(0)); + // Modify by name + ModifyObjectRequest modReq = ModifyObjectRequest.newBuilder() + .setObjectType(DefaultObjectType.ROLE_TYPE) + .setName("role001") + .addModifications(ItemDeltaMessage.newBuilder() + .setPath("name") + .addPrismValuesToReplace(PrismValueMessage.newBuilder() + .setProperty(PrismPropertyValueMessage.newBuilder() + .setPolyString(PolyStringMessage.newBuilder() + .setOrig("role001_mod") + ) + ) + ) + ) + .build(); + + defaultServiceAccountStub.modifyObject(modReq); + + // Get by name + req2 = GetRoleRequest.newBuilder() + .setName("role001_mod") + .build(); + + res2 = defaultServiceAccountStub.getRole(req2); + + assertEquals("role001_mod", res2.getResult().getName().getOrig()); + // Search - SearchRolesResponse res3 = stub.searchRoles(SearchRequest.newBuilder() + SearchRolesResponse res3 = defaultServiceAccountStub.searchRoles(SearchRequest.newBuilder() .setQuery(QueryMessage.newBuilder() .setFilter(ObjectFilterMessage.newBuilder() .setEq(FilterEntryMessage.newBuilder() @@ -930,7 +887,7 @@ void role() throws Exception { assertEquals("testRole", res3.getResults(0).getSubtype(0)); // Delete - DeleteObjectResponse res4 = stub.deleteObject(DeleteObjectRequest.newBuilder() + DeleteObjectResponse res4 = defaultServiceAccountStub.deleteObject(DeleteObjectRequest.newBuilder() .setOid(response.getOid()) .setObjectType(DefaultObjectType.ROLE_TYPE) .build()); @@ -940,15 +897,6 @@ void role() throws Exception { @Test void org() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // Add AddOrgRequest request = AddOrgRequest.newBuilder() .setObject(OrgTypeMessage.newBuilder() @@ -958,7 +906,7 @@ void org() throws Exception { ) .build(); - AddObjectResponse response = stub.addOrg(request); + AddObjectResponse response = defaultServiceAccountStub.addOrg(request); assertNotNull(response.getOid()); @@ -967,13 +915,41 @@ void org() throws Exception { .setOid(response.getOid()) .build(); - GetOrgResponse res2 = stub.getOrg(req2); + GetOrgResponse res2 = defaultServiceAccountStub.getOrg(req2); assertEquals("org001", res2.getResult().getName().getOrig()); assertEquals(1, res2.getResult().getDisplayOrder()); + // Modify by name + ModifyObjectRequest modReq = ModifyObjectRequest.newBuilder() + .setObjectType(DefaultObjectType.ORG_TYPE) + .setName("org001") + .addModifications(ItemDeltaMessage.newBuilder() + .setPath("name") + .addPrismValuesToReplace(PrismValueMessage.newBuilder() + .setProperty(PrismPropertyValueMessage.newBuilder() + .setPolyString(PolyStringMessage.newBuilder() + .setOrig("org001_mod") + ) + ) + ) + ) + .build(); + + defaultServiceAccountStub.modifyObject(modReq); + + // Get by name + req2 = GetOrgRequest.newBuilder() + .setName("org001_mod") + .build(); + + res2 = defaultServiceAccountStub.getOrg(req2); + + assertEquals("org001_mod", res2.getResult().getName().getOrig()); + assertEquals(1, res2.getResult().getDisplayOrder()); + // Search - SearchOrgsResponse res3 = stub.searchOrgs(SearchRequest.newBuilder() + SearchOrgsResponse res3 = defaultServiceAccountStub.searchOrgs(SearchRequest.newBuilder() .setQuery(QueryMessage.newBuilder() .setFilter(ObjectFilterMessage.newBuilder() .setEq(FilterEntryMessage.newBuilder() @@ -984,9 +960,9 @@ void org() throws Exception { assertEquals(1, res3.getNumberOfAllResults()); assertEquals(1, res3.getResults(0).getDisplayOrder()); - // Delete - DeleteObjectResponse res4 = stub.deleteObject(DeleteObjectRequest.newBuilder() - .setOid(response.getOid()) + // Delete by name + DeleteObjectResponse res4 = defaultServiceAccountStub.deleteObject(DeleteObjectRequest.newBuilder() + .setName("org001_mod") .setObjectType(DefaultObjectType.ORG_TYPE) .build()); @@ -995,15 +971,6 @@ void org() throws Exception { @Test void service() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // Add AddServiceRequest request = AddServiceRequest.newBuilder() .setObject(ServiceTypeMessage.newBuilder() @@ -1013,7 +980,7 @@ void service() throws Exception { ) .build(); - AddObjectResponse response = stub.addService(request); + AddObjectResponse response = defaultServiceAccountStub.addService(request); assertNotNull(response.getOid()); @@ -1022,13 +989,44 @@ void service() throws Exception { .setOid(response.getOid()) .build(); - GetServiceResponse res2 = stub.getService(req2); + GetServiceResponse res2 = defaultServiceAccountStub.getService(req2); assertEquals("service001", res2.getResult().getName().getOrig()); assertEquals("https://example.com", res2.getResult().getUrl()); + // Modify by name + ModifyObjectRequest modReq = ModifyObjectRequest.newBuilder() + .setObjectType(DefaultObjectType.SERVICE_TYPE) + .setName("service001") + .addModifications(ItemDeltaMessage.newBuilder() + .setItemPath(ItemPathMessage.newBuilder() + .addPath(QNameMessage.newBuilder() + .setLocalPart("name") + ) + ) + .addPrismValuesToReplace(PrismValueMessage.newBuilder() + .setProperty(PrismPropertyValueMessage.newBuilder() + .setPolyString(PolyStringMessage.newBuilder() + .setOrig("service001_mod") + ) + ) + ) + ) + .build(); + + defaultServiceAccountStub.modifyObject(modReq); + + // Get by name + req2 = GetServiceRequest.newBuilder() + .setName("service001_mod") + .build(); + + res2 = defaultServiceAccountStub.getService(req2); + + assertEquals("service001_mod", res2.getResult().getName().getOrig()); + // Search - SearchServicesResponse res3 = stub.searchServices(SearchRequest.newBuilder() + SearchServicesResponse res3 = defaultServiceAccountStub.searchServices(SearchRequest.newBuilder() .setQuery(QueryMessage.newBuilder() .setFilter(ObjectFilterMessage.newBuilder() .setEq(FilterEntryMessage.newBuilder() @@ -1040,7 +1038,7 @@ void service() throws Exception { assertEquals("https://example.com", res3.getResults(0).getUrl()); // Delete - DeleteObjectResponse res4 = stub.deleteObject(DeleteObjectRequest.newBuilder() + DeleteObjectResponse res4 = defaultServiceAccountStub.deleteObject(DeleteObjectRequest.newBuilder() .setOid(response.getOid()) .setObjectType(DefaultObjectType.SERVICE_TYPE) .build()); @@ -1049,22 +1047,30 @@ void service() throws Exception { } @Test - void lookupTable() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); + void recompute() throws Exception { + // Recompute user + defaultServiceAccountStub.recomputeObject(RecomputeObjectRequest.newBuilder() + .setObjectType(DefaultObjectType.USER_TYPE) + .setOid("00000000-0000-0000-0000-000000000002") + .build() + ); - stub = MetadataUtils.attachHeaders(stub, headers); + // Recompute role by name + defaultServiceAccountStub.recomputeObject(RecomputeObjectRequest.newBuilder() + .setObjectType(DefaultObjectType.ROLE_TYPE) + .setName(GRPC_SERVICE_ROLE_NAME) + .build() + ); + } + @Test + void lookupTable() throws Exception { // Get with default options GetLookupTableRequest req = GetLookupTableRequest.newBuilder() .setName("Languages") .build(); - GetLookupTableResponse res = stub.getLookupTable(req); + GetLookupTableResponse res = defaultServiceAccountStub.getLookupTable(req); assertEquals("00000000-0000-0000-0000-000000000200", res.getResult().getOid()); assertEquals("1", res.getResult().getVersion()); @@ -1079,7 +1085,7 @@ void lookupTable() throws Exception { .addInclude("row") .build(); - res = stub.getLookupTable(req); + res = defaultServiceAccountStub.getLookupTable(req); assertEquals("00000000-0000-0000-0000-000000000220", res.getResult().getOid()); assertEquals("1", res.getResult().getVersion()); @@ -1106,7 +1112,7 @@ void lookupTable() throws Exception { ) .build(); - res = stub.getLookupTable(req); + res = defaultServiceAccountStub.getLookupTable(req); assertEquals("00000000-0000-0000-0000-000000000200", res.getResult().getOid()); assertEquals("1", res.getResult().getVersion()); @@ -1137,7 +1143,7 @@ void lookupTable() throws Exception { ) .build(); - res = stub.getLookupTable(req); + res = defaultServiceAccountStub.getLookupTable(req); assertEquals("00000000-0000-0000-0000-000000000200", res.getResult().getOid()); assertEquals("1", res.getResult().getVersion()); @@ -1198,7 +1204,7 @@ void lookupTable() throws Exception { ) .build(); - ModifyObjectResponse addRowRes = stub.modifyObject(addRowReq); + ModifyObjectResponse addRowRes = defaultServiceAccountStub.modifyObject(addRowReq); req = GetLookupTableRequest.newBuilder() .setName("Languages") @@ -1210,7 +1216,7 @@ void lookupTable() throws Exception { ) .build(); - res = stub.getLookupTable(req); + res = defaultServiceAccountStub.getLookupTable(req); rows = res.getResult().getRowList(); assertEquals(1, rows.size()); @@ -1239,9 +1245,9 @@ void lookupTable() throws Exception { .addOptions("raw") .build(); - ModifyObjectResponse updateRowRes = stub.modifyObject(updateRowReq); + ModifyObjectResponse updateRowRes = defaultServiceAccountStub.modifyObject(updateRowReq); - res = stub.getLookupTable(req); + res = defaultServiceAccountStub.getLookupTable(req); rows = res.getResult().getRowList(); assertEquals(1, rows.size()); @@ -1267,9 +1273,9 @@ void lookupTable() throws Exception { .addOptions("raw") .build(); - ModifyObjectResponse deleteRowRes = stub.modifyObject(deleteRowReq); + ModifyObjectResponse deleteRowRes = defaultServiceAccountStub.modifyObject(deleteRowReq); - res = stub.getLookupTable(req); + res = defaultServiceAccountStub.getLookupTable(req); rows = res.getResult().getRowList(); assertEquals(0, rows.size()); @@ -1277,15 +1283,6 @@ void lookupTable() throws Exception { @Test void getSequenceCounter() throws Exception { - SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); - - String token = Base64.getEncoder().encodeToString("Administrator:5ecr3t".getBytes("UTF-8")); - - Metadata headers = new Metadata(); - headers.put(Constant.AuthorizationMetadataKey, "Basic " + token); - - stub = MetadataUtils.attachHeaders(stub, headers); - // create new SequenceType AddObjectRequest newSeqReq = AddObjectRequest.newBuilder() .setType(QNameMessage.newBuilder().setLocalPart("SequenceType")) @@ -1324,20 +1321,224 @@ void getSequenceCounter() throws Exception { ) .build(); - AddObjectResponse addSeqRes = stub.addObject(newSeqReq); + AddObjectResponse addSeqRes = defaultServiceAccountStub.addObject(newSeqReq); // increment GetSequenceCounterRequest req = GetSequenceCounterRequest.newBuilder() .setName("Unix UID numbers") .build(); - GetSequenceCounterResponse res = stub.getSequenceCounter(req); + GetSequenceCounterResponse res = defaultServiceAccountStub.getSequenceCounter(req); assertEquals(1001, res.getResult()); // increment - res = stub.getSequenceCounter(req); + res = defaultServiceAccountStub.getSequenceCounter(req); assertEquals(1002, res.getResult()); } + + // Utilities + private static void addGrpcServiceAccount(String username, String password) { + SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = newStubByAdministrator(); + + // Add role with authorization for REST API which is required for gRPC calling + AddObjectRequest addRoleRequest = AddObjectRequest.newBuilder() + .setObjectType(DefaultObjectType.ROLE_TYPE) + .setObject(PrismContainerMessage.newBuilder() + .addValues(PrismContainerValueMessage.newBuilder() + .putValue("name", ItemMessage.newBuilder() + .setProperty(PrismPropertyMessage.newBuilder() + .addValues(PrismPropertyValueMessage.newBuilder() + .setPolyString(PolyStringMessage.newBuilder() + .setOrig(GRPC_SERVICE_ROLE_NAME) + ) + ) + ) + .build() + ) + .putValue("authorization", ItemMessage.newBuilder() + .setContainer(PrismContainerMessage.newBuilder() + .addValues(PrismContainerValueMessage.newBuilder() + .putValue("action", ItemMessage.newBuilder() + .setProperty(PrismPropertyMessage.newBuilder() + .addValues(PrismPropertyValueMessage.newBuilder() + .setString("http://midpoint.evolveum.com/xml/ns/public/security/authorization-rest-3#all") + ) + ) + .build() + ) + ) + .addValues(PrismContainerValueMessage.newBuilder() + .putValue("action", ItemMessage.newBuilder() + .setProperty(PrismPropertyMessage.newBuilder() + .addValues(PrismPropertyValueMessage.newBuilder() + .setString("http://midpoint.evolveum.com/xml/ns/public/security/authorization-rest-3#proxy") + ) + ) + .build() + ) + .putValue("object", ItemMessage.newBuilder() + .setContainer(PrismContainerMessage.newBuilder() + .addValues(PrismContainerValueMessage.newBuilder() + .putValue("type", ItemMessage.newBuilder() + .setProperty(PrismPropertyMessage.newBuilder() + .addValues(PrismPropertyValueMessage.newBuilder() + .setString("UserType") + ) + ) + .build() + ) + ) + ) + .build() + ) + ) + ) + .build() + ) + ) + ) + .build(); + ; + AddObjectResponse addRoleResponse = stub.addObject(addRoleRequest); + + assertNotNull(addRoleResponse.getOid()); + + GRPC_SERVICE_ROLE_OID = addRoleResponse.getOid(); + + // Add user with added role assignment + AddUserRequest addUserRequest = AddUserRequest.newBuilder() + .setProfile(UserTypeMessage.newBuilder() + .setName(PolyStringMessage.newBuilder().setOrig(username)) + .addAssignment(AssignmentMessage.newBuilder() + .setTargetRef(ReferenceMessage.newBuilder() + .setOid(addRoleResponse.getOid()) + .setObjectType(DefaultObjectType.ROLE_TYPE) + ) + ) + ) + .build(); + + AddUserResponse response = stub.addUser(addUserRequest); + + assertNotNull(response.getOid()); + + // Save password to the service account + ModifyUserRequest modifyUserRequest = ModifyUserRequest.newBuilder() + .setOid(response.getOid()) + .addModifications( + UserItemDeltaMessage.newBuilder() + .setPath("credentials/password/value") + .addValuesToReplace(password) + .build() + ) + .build(); + + stub.modifyUser(modifyUserRequest); + } + + private static String addUser(String username, String familyName, String... roleOid) { + SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = newStubByAdministrator(); + + UserTypeMessage.Builder builder = UserTypeMessage.newBuilder() + .setName(PolyStringMessage.newBuilder().setOrig(username)) + .setFamilyName(PolyStringMessage.newBuilder().setOrig(familyName).build()); + + // Role assignment + Arrays.stream(roleOid).forEach(oid -> { + builder.addAssignment(AssignmentMessage.newBuilder() + .setTargetRef(ReferenceMessage.newBuilder() + .setOid(oid) + .setObjectType(DefaultObjectType.ROLE_TYPE) + ) + ); + }); + + // Add + AddUserRequest addUserRequest = AddUserRequest.newBuilder() + .setProfile(builder) + .build(); + + AddUserResponse response = stub.addUser(addUserRequest); + + assertNotNull(response.getOid()); + + // Save default password + ModifyUserRequest modifyUserRequest = ModifyUserRequest.newBuilder() + .setOid(response.getOid()) + .addModifications( + UserItemDeltaMessage.newBuilder() + .setPath("credentials/password/value") + .addValuesToReplace("password") + .build() + ) + .build(); + + stub.modifyUser(modifyUserRequest); + + return response.getOid(); + } + + private static void deleteObject(DefaultObjectType type, String name) { + SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = newStubByAdministrator(); + stub.deleteObject(DeleteObjectRequest.newBuilder() + .setName(name) + .setObjectType(type) + .build()); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newStubByAdministrator() { + return newStub("Administrator", "5ecr3t", false); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newStub(String username, String password) { + return newStub(username, password, false); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newStub(String username, String password, boolean runPrivileged) { + return newStub(username, password, null, null, runPrivileged); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newStubWithSwitchUserByOid(String username, String password, String switchUserOid, boolean runPrivileged) { + return newStub(username, password, switchUserOid, null, runPrivileged); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newStubWithSwitchUserByUsername(String username, String password, String switchUsername, boolean runPrivileged) { + return newStub(username, password, null, switchUsername, runPrivileged); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newDefaultStubWithSwitchUserByOid(String switchUsername, boolean runPrivileged) { + return newStub(GRPC_SERVICE_ACCOUNT_NAME, "password", null, switchUsername, runPrivileged); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newDefaultStubWithSwitchUserByUsername(String switchUsername, boolean runPrivileged) { + return newStub(GRPC_SERVICE_ACCOUNT_NAME, "password", null, switchUsername, runPrivileged); + } + + private static SelfServiceResourceGrpc.SelfServiceResourceBlockingStub newStub(String username, String password, String switchUserOid, String switchUsername, boolean runPrivileged) { + SelfServiceResourceGrpc.SelfServiceResourceBlockingStub stub = SelfServiceResourceGrpc.newBlockingStub(channel); + + Metadata headers = new Metadata(); + + final StringBuilder tmp = new StringBuilder(); + tmp.append(username); + tmp.append(":"); + tmp.append(password); + + headers.put(Constant.AuthorizationMetadataKey, "Basic " + + Base64.getEncoder().encodeToString(tmp.toString().getBytes(Charset.forName("UTF-8")))); + if (switchUsername != null) { + headers.put(Constant.SwitchToPrincipalByNameMetadataKey, switchUsername); + } else if (switchUserOid != null) { + headers.put(Constant.SwitchToPrincipalMetadataKey, switchUserOid); + } + if (runPrivileged) { + headers.put(Constant.RunPrivilegedMetadataKey, "true"); + } + + SelfServiceResourceGrpc.SelfServiceResourceBlockingStub authStub = MetadataUtils.attachHeaders(stub, headers); + + return authStub; + } } \ No newline at end of file diff --git a/self-services/src/test/java/jp/openstandia/midpoint/grpc/TypeConverterTest.java b/self-services/src/test/java/jp/openstandia/midpoint/grpc/TypeConverterTest.java index 12f068a..046eb02 100644 --- a/self-services/src/test/java/jp/openstandia/midpoint/grpc/TypeConverterTest.java +++ b/self-services/src/test/java/jp/openstandia/midpoint/grpc/TypeConverterTest.java @@ -1,14 +1,24 @@ package jp.openstandia.midpoint.grpc; +import com.evolveum.midpoint.prism.PrismService; +import com.evolveum.midpoint.prism.impl.PrismContextImpl; +import com.evolveum.midpoint.prism.impl.schema.SchemaRegistryImpl; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class TypeConverterTest { + @BeforeEach + void before() { + PrismContextImpl prismContext = PrismContextImpl.create(new SchemaRegistryImpl()); + PrismService.get().prismContext(prismContext); + } + @Test public void toItemName() { ItemName name = TypeConverter.toItemName(DefaultUserTypePath.F_NAME); diff --git a/server/pom.xml b/server/pom.xml index cfb810b..fca32d3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -39,16 +39,9 @@ grpc-spring-boot-starter compile - - org.springframework.boot - spring-boot-starter-oauth2-resource-server - compile - com.evolveum.midpoint.gui admin-gui - jar - classes provided diff --git a/server/src/main/java/jp/openstandia/midpoint/grpc/AbstractGrpcAuthenticationInterceptor.java b/server/src/main/java/jp/openstandia/midpoint/grpc/AbstractGrpcAuthenticationInterceptor.java index 7cdab39..a9d08ff 100644 --- a/server/src/main/java/jp/openstandia/midpoint/grpc/AbstractGrpcAuthenticationInterceptor.java +++ b/server/src/main/java/jp/openstandia/midpoint/grpc/AbstractGrpcAuthenticationInterceptor.java @@ -1,9 +1,8 @@ package jp.openstandia.midpoint.grpc; -import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.impl.lens.AssignmentCollector; +import com.evolveum.midpoint.model.impl.lens.LoginAssignmentCollector; import com.evolveum.midpoint.model.impl.security.SecurityHelper; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; @@ -57,9 +56,6 @@ public abstract class AbstractGrpcAuthenticationInterceptor implements ServerInt @Autowired PrismContext prismContext; - @Autowired - ModelService modelService; - @Autowired SecurityEnforcer securityEnforcer; @@ -70,7 +66,7 @@ public abstract class AbstractGrpcAuthenticationInterceptor implements ServerInt TaskManager taskManager; @Autowired - AssignmentCollector assignmentCollector; + LoginAssignmentCollector assignmentCollector; @Override public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { @@ -135,7 +131,7 @@ protected ServerCall.Listener doProcess(ServerCall newAuthorities = new ArrayList(); newAuthorities.add(privilegedAuthorization); - PreAuthenticatedAuthenticationToken newAuthorization = new PreAuthenticatedAuthenticationToken(newPrincipal, (Object) null, newAuthorities); + PreAuthenticatedAuthenticationToken newAuthorization = new PreAuthenticatedAuthenticationToken(newMidPointPrincipal, (Object) null, newAuthorities); LOGGER.trace("NEW auth {}", newAuthorization); return newAuthorization; } - private Authorization createPrivilegedAuthorization() { - AuthorizationType authorizationType = new AuthorizationType(); - authorizationType.getAction().add(AuthorizationConstants.AUTZ_ALL_URL); - return new Authorization(authorizationType); - } - protected abstract String getType(); protected abstract Authentication authenticate(ConnectionEnvironment connEnv, Task task, String header); @@ -228,12 +209,12 @@ protected Authentication authenticateSwitchUser(PrismObject // Don't need to collect authorization if running privileged mode if (!runPrivileged) { - Collection> evaluatedAssignments = assignmentCollector.collect(user, true, task, task.getResult()); + Collection evaluatedAssignments = assignmentCollector.collect(user, task, task.getResult()); Collection authorizations = principal.getAuthorities(); - for (EvaluatedAssignment assignment : evaluatedAssignments) { + for (EvaluatedAssignment assignment : evaluatedAssignments) { if (assignment.isValid()) { for (Authorization autz : assignment.getAuthorizations()) { - authorizations.add(autz.clone()); + principal.addAuthorization(autz.clone()); } } } @@ -250,6 +231,12 @@ protected Authentication authenticateSwitchUser(PrismObject .withDescription(e.getMessage()) .asRuntimeException(); throw exception; + } catch (ConfigurationException e) { + securityHelper.auditLoginFailure(user.getName().getOrig(), user.asObjectable(), connEnv, "Configuration error: " + e.getMessage()); + StatusRuntimeException exception = Status.INVALID_ARGUMENT + .withDescription(e.getMessage()) + .asRuntimeException(); + throw exception; } } @@ -260,7 +247,7 @@ protected void authorizeUser(Authentication auth, String authorization, FocusTyp SecurityContextHolder.getContext().setAuthentication(auth); // authorize for proxy - securityEnforcer.authorize(authorization, null, AuthorizationParameters.Builder.buildObject(proxyUser), null, task, task.getResult()); + securityEnforcer.authorize(authorization, null, AuthorizationParameters.Builder.buildObject(proxyUser), SecurityEnforcer.Options.create(), task, task.getResult()); } catch (SecurityViolationException e) { securityHelper.auditLoginFailure(user.getName().getOrig(), user, connEnv, "Not authorized"); throw Status.PERMISSION_DENIED @@ -281,9 +268,9 @@ protected PrismObject findByOid(Authentication auth, String try { SecurityContextHolder.getContext().setAuthentication(auth); - PrismObject user = modelService.getObject(UserType.class, oid, null, task, result); + PrismObject user = GrpcServerConfiguration.getApplication().getRepositoryService().getObject(UserType.class, oid, null, result); return user; - } catch (SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + } catch (SchemaException | ObjectNotFoundException e) { LOGGER.trace("Exception while authenticating user identified with oid: '{}' to gRPC service: {}", oid, e.getMessage(), e); throw Status.UNAUTHENTICATED .withDescription(e.getMessage()) diff --git a/server/src/main/java/jp/openstandia/midpoint/grpc/BasicAuthenticationInterceptor.java b/server/src/main/java/jp/openstandia/midpoint/grpc/BasicAuthenticationInterceptor.java index 8bbc052..0e51c32 100644 --- a/server/src/main/java/jp/openstandia/midpoint/grpc/BasicAuthenticationInterceptor.java +++ b/server/src/main/java/jp/openstandia/midpoint/grpc/BasicAuthenticationInterceptor.java @@ -1,7 +1,8 @@ package jp.openstandia.midpoint.grpc; -import com.evolveum.midpoint.model.api.AuthenticationEvaluator; -import com.evolveum.midpoint.model.api.context.PasswordAuthenticationContext; +import com.evolveum.midpoint.authentication.api.evaluator.AuthenticationEvaluator; +import com.evolveum.midpoint.authentication.api.evaluator.context.PasswordAuthenticationContext; +import com.evolveum.midpoint.authentication.impl.FocusAuthenticationResultRecorder; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.security.api.AuthorizationConstants; import com.evolveum.midpoint.security.api.ConnectionEnvironment; @@ -13,23 +14,24 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import io.grpc.Metadata; import io.grpc.Status; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; @Component -@ConditionalOnMissingBean(JWTAuthenticationInterceptor.class) public class BasicAuthenticationInterceptor extends AbstractGrpcAuthenticationInterceptor { private static final Trace LOGGER = TraceManager.getTrace(BasicAuthenticationInterceptor.class); private static final String TYPE = "Basic"; @Autowired - transient AuthenticationEvaluator passwordAuthenticationEvaluator; + transient AuthenticationEvaluator passwordAuthenticationEvaluator; + + @Autowired + FocusAuthenticationResultRecorder authenticationRecorder; @Override public String getType() { @@ -90,10 +92,13 @@ protected String[] extractAndDecodeBasicAuthzHeader(String header) { private UsernamePasswordAuthenticationToken authenticateUser(ConnectionEnvironment connEnv, String username, String password) { LOGGER.debug("Start authenticateUser: {}", username); + UsernamePasswordAuthenticationToken token = null; try { // login session is recorded here // TODO Use custom evaluator here because it takes several tens of ms - UsernamePasswordAuthenticationToken token = passwordAuthenticationEvaluator.authenticate(connEnv, new PasswordAuthenticationContext(username, password, UserType.class)); + connEnv.setSequenceIdentifier("grpc"); + connEnv.setModuleIdentifier("httpBasic"); + token = passwordAuthenticationEvaluator.authenticate(connEnv, new PasswordAuthenticationContext(username, password, UserType.class)); return token; } catch (AuthenticationException ex) { LOGGER.info("Not authenticated. user: {}, reason: {}", username, ex.getMessage()); @@ -102,7 +107,22 @@ private UsernamePasswordAuthenticationToken authenticateUser(ConnectionEnvironme .withCause(ex) .asRuntimeException(); } finally { + writeRecord(connEnv, token, username); LOGGER.debug("End authenticateUser: {}", username); } } + + // Based on com.evolveum.midpoint.authentication.impl.filter.SequenceAuditFilter#writeRecord + private void writeRecord(ConnectionEnvironment connEnv, UsernamePasswordAuthenticationToken token, String username) { + if (token != null) { + MidPointPrincipal mpPrincipal = token.getPrincipal() instanceof MidPointPrincipal ? (MidPointPrincipal) token.getPrincipal() : null; + boolean isAuthenticated = token.isAuthenticated(); + if (isAuthenticated) { + authenticationRecorder.recordSequenceAuthenticationSuccess(mpPrincipal, connEnv); + } + } else { + authenticationRecorder.recordSequenceAuthenticationFailure(username, null, null, + "invalid_token", connEnv); + } + } } diff --git a/server/src/main/java/jp/openstandia/midpoint/grpc/BuilderWrapper.java b/server/src/main/java/jp/openstandia/midpoint/grpc/BuilderWrapper.java index 1420511..657b2dd 100644 --- a/server/src/main/java/jp/openstandia/midpoint/grpc/BuilderWrapper.java +++ b/server/src/main/java/jp/openstandia/midpoint/grpc/BuilderWrapper.java @@ -49,7 +49,7 @@ public BuilderWrapper selectOptions(Collection BuilderWrapper nullSafeWithRetrieve(ItemPath path, V value, Task t) { - if (!SelectorOptions.hasToLoadPath(path, options, !this.hasInclude)) { + if (!SelectorOptions.hasToIncludePath(path, options, !this.hasInclude)) { return this; } if (value != null) { diff --git a/server/src/main/java/jp/openstandia/midpoint/grpc/JWTAuthenticationInterceptor.java b/server/src/main/java/jp/openstandia/midpoint/grpc/JWTAuthenticationInterceptor.java deleted file mode 100644 index 0994693..0000000 --- a/server/src/main/java/jp/openstandia/midpoint/grpc/JWTAuthenticationInterceptor.java +++ /dev/null @@ -1,129 +0,0 @@ -package jp.openstandia.midpoint.grpc; - -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.security.api.ConnectionEnvironment; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.security.MidPointApplication; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.NodeType; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import io.grpc.Metadata; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition; -import org.springframework.context.annotation.Conditional; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtValidationException; -import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; -import org.springframework.stereotype.Component; - -@Component -@Conditional(IssuerUriCondition.class) -public class JWTAuthenticationInterceptor extends AbstractGrpcAuthenticationInterceptor { - - private static final Trace LOGGER = TraceManager.getTrace(MidPointApplication.class); - - private static final String TYPE = "Bearer"; - - - @Autowired - JwtDecoder jwtDecoder; - - @Value("${spring.security.oauth2.resourceserver.validIssuer}") - String validIssuer; - - @Value("${spring.security.oauth2.resourceserver.validAudience}") - String validAudience; - - public String getType() { - return TYPE; - } - - @Override - public Authentication authenticate(ConnectionEnvironment connEnv, Task task, String header) { - String token = extractHeader(header, TYPE); - - Jwt jwt; - try { - jwt = jwtDecoder.decode(token); - } catch (JwtValidationException e) { - StatusRuntimeException error = Status.UNAUTHENTICATED - .withDescription(INVALID_TOKEN) - .withCause(e) - .asRuntimeException(); - throw error; - } catch (RuntimeException e) { - StatusRuntimeException error = Status.INTERNAL - .withDescription(INTERNAL_ERROR) - .withCause(e) - .asRuntimeException(); - throw error; - } - - // Validate issuer - if (!jwt.getIssuer().toString().equalsIgnoreCase(validIssuer)) { - StatusRuntimeException e = Status.UNAUTHENTICATED - .withDescription(INVALID_TOKEN) - .asRuntimeException(); - throw e; - } - - // Validate audience - if (jwt.getAudience().stream().noneMatch(x -> x.equals(validAudience))) { - StatusRuntimeException e = Status.UNAUTHENTICATED - .withDescription(INVALID_TOKEN) - .asRuntimeException(); - throw e; - } - - // Audit clientId or sub of JWT - NodeType client = new NodeType(); - if (jwt.getClaims().containsKey("clientId")) { - client.setName(PolyStringType.fromOrig(jwt.getClaimAsString("clientId"))); - } else { - client.setName(PolyStringType.fromOrig(jwt.getSubject())); - } - securityHelper.auditLoginSuccess(client, connEnv); - - return new BearerTokenAuthenticationToken(token); - } - - @Override - protected void authorizeClient(Authentication auth, ConnectionEnvironment connEnv, Task task) { - // Do nothing. - // Currently it supports client credentials flow only. - // It means the bearer token doesn't bound to an user in midpoint. - // TODO should check scope of JWT? - } - - @Override - protected Authentication switchToUser(Authentication auth, Metadata headers, boolean runPrivileged, ConnectionEnvironment connEnv, Task task) { - String switchUser = headers.get(Constant.SwitchToPrincipalMetadataKey); - String switchUserByName = headers.get(Constant.SwitchToPrincipalByNameMetadataKey); - - // Find proxy user - PrismObject authorizedUser; - if (StringUtils.isNotBlank(switchUser)) { - authorizedUser = findByOid(auth, switchUser, task); - } else if (StringUtils.isNotBlank(switchUserByName)) { - authorizedUser = findByUsername(auth, switchUserByName, task); - } else { - // JWT authentication needs proxy user always! - StatusRuntimeException e = Status.UNAUTHENTICATED - .withDescription(INVALID_REQUEST) - .asRuntimeException(); - throw e; - } - - // Don't check authorization of client user because the bearer token doesn't bound to an user in midpoint. - - return authenticateSwitchUser(authorizedUser, runPrivileged, connEnv, task); - } -} diff --git a/server/src/main/java/jp/openstandia/midpoint/grpc/MidPointGrpcService.java b/server/src/main/java/jp/openstandia/midpoint/grpc/MidPointGrpcService.java index 364b030..a18c390 100644 --- a/server/src/main/java/jp/openstandia/midpoint/grpc/MidPointGrpcService.java +++ b/server/src/main/java/jp/openstandia/midpoint/grpc/MidPointGrpcService.java @@ -9,20 +9,21 @@ import com.evolveum.midpoint.util.logging.TraceManager; import io.grpc.Metadata; import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import jakarta.servlet.http.HttpServletRequestWrapper; import org.lognet.springboot.grpc.recovery.GRpcExceptionHandler; import org.lognet.springboot.grpc.recovery.GRpcExceptionScope; +import org.lognet.springboot.grpc.recovery.GRpcServiceAdvice; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpSession; import javax.xml.namespace.QName; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -67,6 +68,24 @@ default T runTask(MidPointTask task) { default void handlePolicyViolationException(Metadata responseHeaders, PolicyViolationException e) { } + /** + * grpc-spring-boot-starter v5.0 depends on jakarta.validation. + * Before v5.0, it depends on javax.validation. + * Since javax.validation can be used in midPoint, org.lognet.springboot.grpc.autoconfigure.GRpcValidationConfiguration + * is registered as global gRPC Exception Handler. + * From v5.0, we need to register own global gRPC Exception handler as dummy because of missing jakarta.validation. + */ + @GRpcServiceAdvice + static class DummyErrorHandler { + @GRpcExceptionHandler + public Status handle(DummyException e, GRpcExceptionScope scope) { + return Status.UNKNOWN; + } + + static class DummyException extends RuntimeException { + } + } + @GRpcExceptionHandler default Status handleException(GrpcServiceException gse, GRpcExceptionScope scope) { Throwable cause = gse.getCause(); @@ -114,6 +133,9 @@ default Status handleException(GrpcServiceException gse, GRpcExceptionScope scop .withDescription(e.getErrorTypeMessage()) .withCause(e); } + if (cause instanceof StatusRuntimeException) { + return ((StatusRuntimeException) cause).getStatus(); + } if (cause instanceof Exception) { return Status.INTERNAL .withDescription(cause.getMessage())