Skip to content

Commit

Permalink
feat: implement public create principal and related tests (#1956)
Browse files Browse the repository at this point in the history
  • Loading branch information
roaminggypsy authored Dec 9, 2024
1 parent 08a2736 commit a121af7
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ kt_jvm_library(
"//src/main/kotlin/org/wfanet/measurement/access/service/internal:errors",
"//src/main/kotlin/org/wfanet/measurement/common/grpc:error_info",
"//src/main/proto/google/rpc:error_details_kt_jvm_proto",
"//src/main/proto/wfa/measurement/access/v1alpha:principal_kt_jvm_proto",
"@wfa_common_jvm//imports/java/io/grpc:api",
],
)
24 changes: 22 additions & 2 deletions src/main/kotlin/org/wfanet/measurement/access/service/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.grpc.Status
import io.grpc.StatusException
import io.grpc.StatusRuntimeException
import org.wfanet.measurement.access.service.internal.Errors as InternalErrors
import org.wfanet.measurement.access.v1alpha.Principal
import org.wfanet.measurement.common.grpc.Errors as CommonErrors
import org.wfanet.measurement.common.grpc.errorInfo

Expand All @@ -45,7 +46,7 @@ object Errors {
RESOURCE_TYPE_NOT_FOUND_IN_PERMISSION,
REQUIRED_FIELD_NOT_SET,
INVALID_FIELD_VALUE,
ETAG_MISMATCH
ETAG_MISMATCH,
}

enum class Metadata(val key: String) {
Expand All @@ -61,7 +62,7 @@ object Errors {
ISSUER("issuer"),
SUBJECT("subject"),
REQUEST_ETAG("requestEtag"),
ETAG("etag")
ETAG("etag"),
}
}

Expand Down Expand Up @@ -153,6 +154,25 @@ class PrincipalNotFoundException(name: String, cause: Throwable? = null) :
cause,
)

class PrincipalAlreadyExistsException(cause: Throwable? = null) :
ServiceException(
Errors.Reason.PRINCIPAL_ALREADY_EXISTS,
"Principal already exists",
emptyMap(),
cause,
)

class PrincipalTypeNotSupportedException(
identityCase: Principal.IdentityCase,
cause: Throwable? = null,
) :
ServiceException(
Errors.Reason.PRINCIPAL_TYPE_NOT_SUPPORTED,
"Principal type ${identityCase.name} not supported",
mapOf(Errors.Metadata.PRINCIPAL_TYPE to identityCase.name),
cause,
)

class PermissionNotFoundException(name: String, cause: Throwable? = null) :
ServiceException(
reason,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ kt_jvm_library(
":resource_conversion",
"//src/main/kotlin/org/wfanet/measurement/access/service:errors",
"//src/main/kotlin/org/wfanet/measurement/access/service:resource_key",
"//src/main/kotlin/org/wfanet/measurement/common/api:resource_ids",
"//src/main/proto/wfa/measurement/access/v1alpha:principals_service_kt_jvm_grpc_proto",
"//src/main/proto/wfa/measurement/internal/access:principals_service_kt_jvm_grpc_proto",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ package org.wfanet.measurement.access.service.v1alpha
import io.grpc.Status
import io.grpc.StatusException
import org.wfanet.measurement.access.service.InvalidFieldValueException
import org.wfanet.measurement.access.service.PrincipalAlreadyExistsException
import org.wfanet.measurement.access.service.PrincipalKey
import org.wfanet.measurement.access.service.PrincipalNotFoundException
import org.wfanet.measurement.access.service.PrincipalTypeNotSupportedException
import org.wfanet.measurement.access.service.RequiredFieldNotSetException
import org.wfanet.measurement.access.service.internal.Errors as InternalErrors
import org.wfanet.measurement.access.v1alpha.CreatePrincipalRequest
import org.wfanet.measurement.access.v1alpha.GetPrincipalRequest
import org.wfanet.measurement.access.v1alpha.Principal
import org.wfanet.measurement.access.v1alpha.PrincipalsGrpcKt
import org.wfanet.measurement.common.api.ResourceIds
import org.wfanet.measurement.internal.access.Principal as InternalPrincipal
import org.wfanet.measurement.internal.access.PrincipalsGrpcKt.PrincipalsCoroutineStub as InternalPrincipalsCoroutineStub
import org.wfanet.measurement.internal.access.createUserPrincipalRequest as internalCreateUserPrincipalRequest
import org.wfanet.measurement.internal.access.getPrincipalRequest as internalGetPrincipalRequest

class PrincipalsService(private val internalPrincipalsStub: InternalPrincipalsCoroutineStub) :
Expand Down Expand Up @@ -75,4 +80,71 @@ class PrincipalsService(private val internalPrincipalsStub: InternalPrincipalsCo

return internalResponse.toPrincipal()
}

override suspend fun createPrincipal(request: CreatePrincipalRequest): Principal {
when (request.principal.identityCase) {
Principal.IdentityCase.USER -> {}
Principal.IdentityCase.TLS_CLIENT ->
throw PrincipalTypeNotSupportedException(request.principal.identityCase)
.asStatusRuntimeException(Status.Code.INVALID_ARGUMENT)
else ->
throw RequiredFieldNotSetException("principal.identity")
.asStatusRuntimeException(Status.Code.INVALID_ARGUMENT)
}

if (request.principal.user.issuer.isEmpty()) {
throw RequiredFieldNotSetException("principal.user.issuer")
.asStatusRuntimeException(Status.Code.INVALID_ARGUMENT)
}

if (request.principal.user.subject.isEmpty()) {
throw RequiredFieldNotSetException("principal.user.subject")
.asStatusRuntimeException(Status.Code.INVALID_ARGUMENT)
}

if (request.principalId.isEmpty()) {
throw RequiredFieldNotSetException("principal_id")
.asStatusRuntimeException(Status.Code.INVALID_ARGUMENT)
}

if (!ResourceIds.RFC_1034_REGEX.matches(request.principalId)) {
throw InvalidFieldValueException("principal_id")
.asStatusRuntimeException(Status.Code.INVALID_ARGUMENT)
}

val internalResponse: InternalPrincipal =
try {
internalPrincipalsStub.createUserPrincipal(
internalCreateUserPrincipalRequest {
principalResourceId = request.principalId
user = request.principal.user.toInternal()
}
)
} catch (e: StatusException) {
throw when (InternalErrors.getReason(e)) {
InternalErrors.Reason.PRINCIPAL_ALREADY_EXISTS ->
PrincipalAlreadyExistsException(e).asStatusRuntimeException(Status.Code.ALREADY_EXISTS)
InternalErrors.Reason.PRINCIPAL_NOT_FOUND,
InternalErrors.Reason.PRINCIPAL_NOT_FOUND_FOR_USER,
InternalErrors.Reason.PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT,
InternalErrors.Reason.PRINCIPAL_TYPE_NOT_SUPPORTED,
InternalErrors.Reason.PERMISSION_NOT_FOUND,
InternalErrors.Reason.PERMISSION_NOT_FOUND_FOR_ROLE,
InternalErrors.Reason.ROLE_NOT_FOUND,
InternalErrors.Reason.ROLE_ALREADY_EXISTS,
InternalErrors.Reason.POLICY_NOT_FOUND,
InternalErrors.Reason.POLICY_NOT_FOUND_FOR_PROTECTED_RESOURCE,
InternalErrors.Reason.POLICY_ALREADY_EXISTS,
InternalErrors.Reason.POLICY_BINDING_MEMBERSHIP_ALREADY_EXISTS,
InternalErrors.Reason.POLICY_BINDING_MEMBERSHIP_NOT_FOUND,
InternalErrors.Reason.RESOURCE_TYPE_NOT_FOUND_IN_PERMISSION,
InternalErrors.Reason.REQUIRED_FIELD_NOT_SET,
InternalErrors.Reason.INVALID_FIELD_VALUE,
InternalErrors.Reason.ETAG_MISMATCH,
null -> Status.INTERNAL.withCause(e).asRuntimeException()
}
}

return internalResponse.toPrincipal()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
/*
* Copyright 2024 The Cross-Media Measurement Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wfanet.measurement.access.service.v1alpha

import org.wfanet.measurement.internal.access.Principal as InternalPrincipal
import org.wfanet.measurement.internal.access.Role as InternalRole
import org.wfanet.measurement.access.service.PermissionKey
import org.wfanet.measurement.access.service.PrincipalKey
import org.wfanet.measurement.access.service.RoleKey
Expand All @@ -11,6 +25,9 @@ import org.wfanet.measurement.access.v1alpha.PrincipalKt.tlsClient
import org.wfanet.measurement.access.v1alpha.Role
import org.wfanet.measurement.access.v1alpha.principal
import org.wfanet.measurement.access.v1alpha.role
import org.wfanet.measurement.internal.access.Principal as InternalPrincipal
import org.wfanet.measurement.internal.access.PrincipalKt.oAuthUser as internalOAuthUser
import org.wfanet.measurement.internal.access.Role as InternalRole

fun InternalPrincipal.toPrincipal(): Principal {
val source = this
Expand Down Expand Up @@ -43,7 +60,15 @@ fun InternalRole.toRole(): Role {
return role {
name = RoleKey(source.roleResourceId).toName()
resourceTypes += source.resourceTypesList
permissions += source.permissionResourceIdsList.map { PermissionKey(it).toName()}
permissions += source.permissionResourceIdsList.map { PermissionKey(it).toName() }
etag = source.etag
}
}

fun Principal.OAuthUser.toInternal(): InternalPrincipal.OAuthUser {
val source = this
return internalOAuthUser {
issuer = source.issuer
subject = source.subject
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ service Principals {
}

// Creates a `Principal`.
//
// Error reasons:
// * `PRINCIPAL_ALREADY_EXISTS`
rpc CreatePrincipal(CreatePrincipalRequest) returns (Principal) {
option (google.api.method_signature) = "principal,principal_id";
}
Expand Down
Loading

0 comments on commit a121af7

Please sign in to comment.