dimas-b commented on code in PR #2197:
URL: https://github.com/apache/polaris/pull/2197#discussion_r2286458959


##########
runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -1135,18 +1136,83 @@ public void deletePrincipal(String name) {
             newSecrets.getPrincipalClientId(), newSecrets.getMainSecret()));
   }
 
+  private @Nonnull PrincipalWithCredentials resetCredentialsHelper(
+      String principalName, String customClientId, String customClientSecret) {
+    PrincipalEntity currentPrincipalEntity =
+        findPrincipalByName(principalName)
+            .orElseThrow(() -> new NotFoundException("Principal %s not found", 
principalName));
+
+    if (FederatedEntities.isFederated(currentPrincipalEntity)) {
+      throw new ValidationException(
+          "Cannot reset credentials for a federated principal: %s", 
principalName);
+    }
+    PolarisPrincipalSecrets currentSecrets =
+        metaStoreManager
+            .loadPrincipalSecrets(getCurrentPolarisContext(), 
currentPrincipalEntity.getClientId())
+            .getPrincipalSecrets();
+    if (currentSecrets == null) {
+      throw new IllegalArgumentException(
+          String.format("Failed to load current secrets for principal '%s'", 
principalName));
+    }
+    PolarisPrincipalSecrets newSecrets =
+        metaStoreManager
+            .resetPrincipalSecrets(
+                getCurrentPolarisContext(),
+                currentPrincipalEntity.getClientId(),

Review Comment:
   `testCreatePrincipalAndResetCredentialsWithCustomValues()` only validates 
that the client ID in _credentials_ got reset properly, but it does not 
validate that the client ID in the principal is in sync with that... or did I 
miss it? :thinking: 



##########
spec/polaris-management-service.yml:
##########
@@ -1248,6 +1283,20 @@ components:
         - currentEntityVersion
         - properties
 
+    ResetPrincipalRequest:
+      type: object
+      properties:
+        clientId:
+          type: string
+          description: >
+            Optional client ID to set for the principal.
+            Must be a valid client ID previously generated by this service.
+        clientSecret:
+          type: string
+          description: >
+            Optional client secret to set for the principal.
+            Must match the secret issued for the given client ID.

Review Comment:
   note sure about the "must match" part... How about "Polaris service 
implementations may impose extra requirements on what is accepted as a secret 
(special chars, length, etc.)".



##########
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java:
##########
@@ -773,6 +773,73 @@ public PolarisPrincipalSecrets generateNewPrincipalSecrets(
     return principalSecrets;
   }
 
+  @Nullable
+  @Override
+  public PolarisPrincipalSecrets resetPrincipalSecrets(
+      @Nonnull PolarisCallContext callCtx,
+      @Nonnull String clientId,
+      long principalId,
+      String customClientId,
+      String customClientSecret,
+      boolean customReset) {
+    PolarisPrincipalSecrets principalSecrets = loadPrincipalSecrets(callCtx, 
clientId);
+
+    // should be found
+    callCtx
+        .getDiagServices()
+        .checkNotNull(
+            principalSecrets,
+            "cannot_find_secrets",
+            "client_id={} principalId={}",
+            clientId,
+            principalId);
+
+    // ensure principal id is matching
+    callCtx
+        .getDiagServices()
+        .check(
+            principalId == principalSecrets.getPrincipalId(),
+            "principal_id_mismatch",
+            "expectedId={} id={}",
+            principalId,
+            principalSecrets.getPrincipalId());
+
+    if (customReset) {
+      principalSecrets =
+          new PolarisPrincipalSecrets(
+              principalSecrets.getPrincipalId(), customClientId, 
customClientSecret, null);
+    } else {
+      principalSecrets.rotateSecrets(principalSecrets.getMainSecretHash());

Review Comment:
   I'm fine with generating and returning random credentials from the reset 
API. However, the API is still a bit confusing because in 
`ResetPrincipalRequest` either `clientId` and `clientSecret` may be set without 
the other, but in code (line 806) execution changes only when both are set. In 
effect, invoking `resetCredentials` only with `clientSecret` still generates a 
random secret, if I'm not mistaken.
   
   Secondary point: calling `principalSecrets.rotateSecrets()` on the "reset" 
call path is still awkward since credential "rotation" may have very specific 
logic in `PolarisPrincipalSecrets`.
   
   I propose to add a new method `PolarisPrincipalSecrets.resetSecrets(String 
clientId, String secret)` returning a new `PolarisPrincipalSecrets`. The 
handling of `null` values in `clientId` and `secret` will be inside that 
`resetSecrets()` method. It will keep `this` immutable and return a new 
`PolarisPrincipalSecrets` object possibly reusing values from the old one if 
they did not get reset. WDYT?



##########
polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java:
##########
@@ -892,6 +892,48 @@ private void revokeGrantRecord(
         : new PrincipalSecretsResult(secrets);
   }
 
+  @Override
+  public @Nonnull PrincipalSecretsResult resetPrincipalSecrets(
+      @Nonnull PolarisCallContext callCtx,
+      @Nonnull String clientId,
+      long principalId,
+      @Nonnull String oldSecretHash,
+      String customClientId,
+      String customClientSecret) {
+    // get metastore we should be using
+    BasePersistence ms = callCtx.getMetaStore();
+
+    // if not found, the principal must have been dropped
+    EntityResult loadEntityResult =
+        loadEntity(
+            callCtx, PolarisEntityConstants.getNullId(), principalId, 
PolarisEntityType.PRINCIPAL);
+    if (loadEntityResult.getReturnStatus() != BaseResult.ReturnStatus.SUCCESS) 
{
+      return new 
PrincipalSecretsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null);
+    }
+
+    PolarisBaseEntity principal = loadEntityResult.getEntity();
+    Map<String, String> internalProps = principal.getInternalPropertiesAsMap();
+
+    boolean customReset = customClientId != null && customClientSecret != null;
+    PolarisPrincipalSecrets secrets =
+        ((IntegrationPersistence) ms)
+            .resetPrincipalSecrets(
+                callCtx, clientId, principalId, customClientId, 
customClientSecret);
+
+    PolarisBaseEntity.Builder principalBuilder = new 
PolarisBaseEntity.Builder(principal);
+    
principalBuilder.internalProperties(PolarisObjectMapperUtil.serializeProperties(internalProps));

Review Comment:
   Maybe `principalBuilder.internalPropertiesAsMap(...)`?
   
   TBH, I'm at a loss as to the purpose of this piece of code. I do not see any 
change to internal properties. Did I miss something?



##########
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java:
##########
@@ -773,6 +773,73 @@ public PolarisPrincipalSecrets generateNewPrincipalSecrets(
     return principalSecrets;
   }
 
+  @Nullable
+  @Override
+  public PolarisPrincipalSecrets resetPrincipalSecrets(
+      @Nonnull PolarisCallContext callCtx,
+      @Nonnull String clientId,
+      long principalId,
+      String customClientId,
+      String customClientSecret,
+      boolean customReset) {
+    PolarisPrincipalSecrets principalSecrets = loadPrincipalSecrets(callCtx, 
clientId);
+
+    // should be found
+    callCtx
+        .getDiagServices()
+        .checkNotNull(

Review Comment:
   Good point... still, if the principal is found, but do not have credentials 
in Polaris (i.e. it's a "federated" principal), returning 500 is not nice in 
this case. 
   
   Unfortunately, I do not see well-defined existing cases for reporting nice 
errors in cases like this. I'm ok with the current impl. but we'll probably 
have to revisit it at some point.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@polaris.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to