This is an automated email from the ASF dual-hosted git repository.
sungwy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 26dcf0b401 core: Refactor PolarisAuthorizer SPI (Phase 1): new
interfaces and utilities (#3760)
26dcf0b401 is described below
commit 26dcf0b401bc6ee5eb6971bd4ef9edb0cfd2675b
Author: Sung Yun <[email protected]>
AuthorDate: Fri Feb 27 19:26:13 2026 -0500
core: Refactor PolarisAuthorizer SPI (Phase 1): new interfaces and
utilities (#3760)
* refactoring to PolarisAuthorizer SPI
* spotless
* request scoped CDI bean RequestAuthorizationState
* AuthorizationDecision
* update resolveSelection logic
* docstring
* introduce guards in resolver when Resolvables were not selected
* Resolver guard fixes
* spotless
* adopt feedback - thanks dmitri
* adopt feedback and add tests
* adopt review feedback
---
.../extension/auth/opa/OpaPolarisAuthorizer.java | 17 +++
.../polaris/core/auth/AuthorizationDecision.java | 44 ++++++
.../polaris/core/auth/AuthorizationRequest.java | 109 +++++++++++++++
.../polaris/core/auth/AuthorizationState.java | 53 +++++++
.../core/auth/AuthorizationTargetBinding.java | 56 ++++++++
.../polaris/core/auth/PolarisAuthorizer.java | 36 +++++
.../polaris/core/auth/PolarisAuthorizerImpl.java | 14 ++
.../apache/polaris/core/auth/PolarisSecurable.java | 47 +++++++
.../resolver/PolarisResolutionManifest.java | 16 +++
.../core/persistence/resolver/Resolvable.java | 44 ++++++
.../core/persistence/resolver/Resolver.java | 153 ++++++++++++++++-----
.../core/auth/AuthorizationRequestTest.java | 88 ++++++++++++
.../polaris/core/persistence/ResolverTest.java | 118 ++++++++++++++++
.../polaris/service/config/ServiceProducers.java | 7 +
14 files changed, 765 insertions(+), 37 deletions(-)
diff --git
a/extensions/auth/opa/impl/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
b/extensions/auth/opa/impl/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
index 6c216121ef..bd26e92595 100644
---
a/extensions/auth/opa/impl/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
+++
b/extensions/auth/opa/impl/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
@@ -41,6 +41,9 @@ import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.iceberg.exceptions.ForbiddenException;
+import org.apache.polaris.core.auth.AuthorizationDecision;
+import org.apache.polaris.core.auth.AuthorizationRequest;
+import org.apache.polaris.core.auth.AuthorizationState;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.auth.PolarisPrincipal;
@@ -95,6 +98,20 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer {
this.objectMapper = objectMapper;
}
+ @Override
+ public void resolveAuthorizationInputs(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request) {
+ throw new UnsupportedOperationException(
+ "resolveAuthorizationInputs is not implemented yet for
OpaPolarisAuthorizer");
+ }
+
+ @Override
+ public AuthorizationDecision authorize(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request) {
+ throw new UnsupportedOperationException(
+ "authorize is not implemented yet for OpaPolarisAuthorizer");
+ }
+
/**
* Authorizes a single target and secondary entity for the given principal
and operation.
*
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationDecision.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationDecision.java
new file mode 100644
index 0000000000..db5b24806b
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationDecision.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.auth;
+
+import jakarta.annotation.Nullable;
+import java.util.Optional;
+import org.apache.polaris.immutables.PolarisImmutable;
+import org.immutables.value.Value;
+
+/** Authorization decision returned by authorizer implementations. */
+@PolarisImmutable
+public interface AuthorizationDecision {
+ AuthorizationDecision ALLOW = ImmutableAuthorizationDecision.of(true,
Optional.empty());
+
+ static AuthorizationDecision allow() {
+ return ALLOW;
+ }
+
+ static AuthorizationDecision deny(@Nullable String message) {
+ return ImmutableAuthorizationDecision.of(false,
Optional.ofNullable(message));
+ }
+
+ @Value.Parameter(order = 1)
+ boolean isAllowed();
+
+ @Value.Parameter(order = 2)
+ Optional<String> getMessage();
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationRequest.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationRequest.java
new file mode 100644
index 0000000000..c02432dc0d
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationRequest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.auth;
+
+import jakarta.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.immutables.PolarisImmutable;
+import org.immutables.value.Value;
+
+/**
+ * Authorization request inputs for pre-authorization and core authorization.
+ *
+ * <p>This wrapper keeps authorization inputs together and conveys the intent
to be authorized via
+ * {@link AuthorizationTargetBinding} target bindings.
+ */
+@PolarisImmutable
+public interface AuthorizationRequest {
+ static AuthorizationRequest of(
+ @Nonnull PolarisPrincipal principal,
+ @Nonnull PolarisAuthorizableOperation operation,
+ @Nonnull List<AuthorizationTargetBinding> targetBindings) {
+ return ImmutableAuthorizationRequest.builder()
+ .principal(principal)
+ .operation(operation)
+ .targetBindings(targetBindings)
+ .build();
+ }
+
+ /** Returns the principal requesting authorization. */
+ @Nonnull
+ PolarisPrincipal getPrincipal();
+
+ /** Returns the operation being authorized. */
+ @Nonnull
+ PolarisAuthorizableOperation getOperation();
+
+ /** Returns the target/secondary target bindings. */
+ @Nonnull
+ List<AuthorizationTargetBinding> getTargetBindings();
+
+ /**
+ * Returns the primary target securables, if any.
+ *
+ * <p>Compatibility accessor derived from {@link #getTargetBindings()}.
+ */
+ @Nonnull
+ @Value.Derived
+ default List<PolarisSecurable> getTargets() {
+ return
getTargetBindings().stream().map(AuthorizationTargetBinding::getTarget).toList();
+ }
+
+ /**
+ * Returns secondary securables, if any.
+ *
+ * <p>Compatibility accessor derived from {@link #getTargetBindings()}.
+ */
+ @Nonnull
+ @Value.Derived
+ default List<PolarisSecurable> getSecondaries() {
+ List<PolarisSecurable> secondaries = new ArrayList<>();
+ for (AuthorizationTargetBinding targetBinding : getTargetBindings()) {
+ if (targetBinding.getSecondary() != null) {
+ secondaries.add(targetBinding.getSecondary());
+ }
+ }
+ return secondaries;
+ }
+
+ default boolean hasSecurableType(PolarisEntityType... types) {
+ for (AuthorizationTargetBinding targetBinding : getTargetBindings()) {
+ if (containsType(targetBinding.getTarget(), types)) {
+ return true;
+ }
+ if (targetBinding.getSecondary() != null
+ && containsType(targetBinding.getSecondary(), types)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static boolean containsType(PolarisSecurable securable, PolarisEntityType...
types) {
+ PolarisEntityType entityType = securable.getEntityType();
+ for (PolarisEntityType type : types) {
+ if (entityType == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java
new file mode 100644
index 0000000000..84213da6cb
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.auth;
+
+import jakarta.annotation.Nonnull;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
+
+/**
+ * Request-scoped authorization state shared across authorization phases.
+ *
+ * <p>Used to carry authorization-specific data, such as a {@link
PolarisResolutionManifest} created
+ * by the caller and populated by the authorizer.
+ */
+public class AuthorizationState {
+ private final AtomicReference<PolarisResolutionManifest> resolutionManifest =
+ new AtomicReference<>();
+
+ /** Returns the request-scoped resolution manifest used for authorization. */
+ @Nonnull
+ public PolarisResolutionManifest getResolutionManifest() {
+ PolarisResolutionManifest manifest = resolutionManifest.get();
+ if (manifest == null) {
+ throw new IllegalStateException("AuthorizationState resolution manifest
is not set");
+ }
+ return manifest;
+ }
+
+ /** Sets the request-scoped resolution manifest used for authorization. */
+ public void setResolutionManifest(@Nonnull PolarisResolutionManifest
resolutionManifest) {
+ Objects.requireNonNull(resolutionManifest, "resolutionManifest");
+ if (!this.resolutionManifest.compareAndSet(null, resolutionManifest)) {
+ throw new IllegalStateException("AuthorizationState resolution manifest
already set");
+ }
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationTargetBinding.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationTargetBinding.java
new file mode 100644
index 0000000000..2e5ae4ab7a
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationTargetBinding.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.auth;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import org.apache.polaris.immutables.PolarisImmutable;
+
+/** A resource binding containing a primary target and optional secondary. */
+@PolarisImmutable
+public interface AuthorizationTargetBinding {
+ static AuthorizationTargetBinding of(
+ @Nonnull PolarisSecurable target, @Nullable PolarisSecurable secondary) {
+ return ImmutableAuthorizationTargetBinding.builder()
+ .target(target)
+ .secondary(secondary)
+ .build();
+ }
+
+ /** Returns the primary target securable for the binding. */
+ @Nonnull
+ PolarisSecurable getTarget();
+
+ /**
+ * Returns the optional secondary securable associated with the target.
+ *
+ * <p>Secondaries are related resources needed to evaluate the authorization
decision but are not
+ * the direct object of the operation. Examples in current Polaris
authorization flows include:
+ *
+ * <ul>
+ * <li>Table rename: the destination namespace (target is the source
table).
+ * <li>Role grants: the grantee role/principal (target may be the role or
the resource being
+ * granted on).
+ * <li>Policy attach/detach: the catalog/namespace/table being attached to
(target is the
+ * policy).
+ * </ul>
+ */
+ @Nullable
+ PolarisSecurable getSecondary();
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java
index 55c3792067..975eae5f03 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizer.java
@@ -22,11 +22,47 @@ import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Set;
+import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
/** Interface for invoking authorization checks. */
public interface PolarisAuthorizer {
+ /**
+ * Resolve authorizer-specific inputs before authorization.
+ *
+ * <p>Implementations may resolve only the entities required for the request
(for example, the
+ * caller principal, principal roles, catalog roles, and requested targets)
and store that state
+ * in {@link AuthorizationState}.
+ *
+ * <p>This method should not perform authorization decisions directly.
+ */
+ void resolveAuthorizationInputs(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request);
+
+ /**
+ * Core authorization entry point for the new SPI.
+ *
+ * <p>Implementations should rely on any required state in {@link
AuthorizationState} and the
+ * intent captured by {@link AuthorizationRequest} (principal, operation,
and target securables).
+ */
+ @Nonnull
+ AuthorizationDecision authorize(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request);
+
+ /**
+ * Convenience method that throws a {@link ForbiddenException} when
authorization is denied.
+ *
+ * <p>Implementations should provide allow/deny decisions via {@link
#authorize}.
+ */
+ default void authorizeOrThrow(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request) {
+ AuthorizationDecision decision = authorize(authzState, request);
+ if (!decision.isAllowed()) {
+ String message = decision.getMessage().orElse("Authorization denied");
+ throw new ForbiddenException("%s", message);
+ }
+ }
void authorizeOrThrow(
@Nonnull PolarisPrincipal polarisPrincipal,
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
index 635f255a8e..d878577f58 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
@@ -744,6 +744,20 @@ public class PolarisAuthorizerImpl implements
PolarisAuthorizer {
this.realmConfig = realmConfig;
}
+ @Override
+ public void resolveAuthorizationInputs(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request) {
+ throw new UnsupportedOperationException(
+ "resolveAuthorizationInputs is not implemented yet for
PolarisAuthorizerImpl");
+ }
+
+ @Override
+ public AuthorizationDecision authorize(
+ @Nonnull AuthorizationState authzState, @Nonnull AuthorizationRequest
request) {
+ throw new UnsupportedOperationException(
+ "authorize is not implemented yet for PolarisAuthorizerImpl");
+ }
+
/**
* Checks whether the {@code grantedPrivilege} is sufficient to confer
{@code desiredPrivilege},
* assuming the privileges are referring to the same securable object. In
other words, whether the
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java
new file mode 100644
index 0000000000..739815fab9
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecurable.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.auth;
+
+import jakarta.annotation.Nonnull;
+import java.util.List;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.immutables.PolarisImmutable;
+
+/**
+ * Intent-only target reference for authorization decisions.
+ *
+ * <p>Represents the target name and entity type without any resolved RBAC
state. This captures the
+ * pure authorization intent of a request. Callers should provide the full
name path as {@link
+ * #getNameParts()} for hierarchical entities.
+ */
+@PolarisImmutable
+public interface PolarisSecurable {
+ static PolarisSecurable of(
+ @Nonnull PolarisEntityType entityType, @Nonnull List<String> nameParts) {
+ return
ImmutablePolarisSecurable.builder().entityType(entityType).nameParts(nameParts).build();
+ }
+
+ /** Returns the entity type of the securable. */
+ @Nonnull
+ PolarisEntityType getEntityType();
+
+ /** Returns the name parts that identify the securable in hierarchical
order. */
+ @Nonnull
+ List<String> getNameParts();
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
index 31d95e7bba..a3965bd4f1 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
@@ -143,6 +143,22 @@ public class PolarisResolutionManifest implements
PolarisResolutionManifestCatal
return primaryResolverStatus;
}
+ /**
+ * Resolves explicitly requested components.
+ *
+ * <p>Selections control which resolver components are executed. Callers are
expected to add paths
+ * or top-level entity names before invoking this method.
+ */
+ public ResolverStatus resolveSelections(Set<Resolvable> selections) {
+ diagnostics.checkNotNull(selections, "resolver_selections_is_null");
+ primaryResolverStatus = primaryResolver.resolveSelections(selections);
+ diagnostics.check(
+ primaryResolverStatus.getStatus()
+ != ResolverStatus.StatusEnum.CALLER_PRINCIPAL_DOES_NOT_EXIST,
+ "caller_principal_does_not_exist_at_resolution_time");
+ return primaryResolverStatus;
+ }
+
public boolean getIsPassthroughFacade() {
return primaryResolver.getIsPassthroughFacade();
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolvable.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolvable.java
new file mode 100644
index 0000000000..28f3f09b9c
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolvable.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.persistence.resolver;
+
+/**
+ * Explicit resolution components for {@link
+ * PolarisResolutionManifest#resolveSelections(java.util.Set)}.
+ *
+ * <p>Selections control which entity groups the resolver should fetch for a
request.
+ */
+public enum Resolvable {
+ /** Resolve the authenticated caller principal entity. */
+ CALLER_PRINCIPAL,
+ /** Resolve the caller's activated principal-role entities. */
+ CALLER_PRINCIPAL_ROLES,
+ /** Resolve catalog-role entities (e.g., roles attached in the reference
catalog). */
+ CATALOG_ROLES,
+ /** Resolve the reference catalog entity. */
+ REFERENCE_CATALOG,
+ /** Resolve explicitly registered paths (via addPath/addPassthroughPath). */
+ REQUESTED_PATHS,
+ /**
+ * Resolve any additional top-level entities explicitly registered via
addTopLevelName, such as
+ * catalog/principal/principal-role names used for authorization beyond the
caller and reference
+ * catalog.
+ */
+ REQUESTED_TOP_LEVEL_ENTITIES
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java
index 6d7f274e0c..532e9aa69e 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java
@@ -238,6 +238,23 @@ public class Resolver {
* getResolvedXYZ() method can be called.
*/
public ResolverStatus resolveAll() {
+ return resolveWithPlan(ResolvePlan.all(referenceCatalogName));
+ }
+
+ /**
+ * Run the resolution process for a subset of resolvables.
+ *
+ * @param selections explicit selection of resolvables to resolve
+ * @return the status of the resolver. If success, the requested entities
have been resolved and
+ * the corresponding getResolvedXYZ() methods can be called.
+ */
+ public ResolverStatus resolveSelections(@Nonnull Set<Resolvable> selections)
{
+ diagnostics.checkNotNull(selections, "resolver_selections_is_null");
+ diagnostics.check(!selections.isEmpty(), "resolver_selections_is_empty");
+ return resolveWithPlan(ResolvePlan.fromSelections(selections,
referenceCatalogName));
+ }
+
+ private ResolverStatus resolveWithPlan(ResolvePlan plan) {
// can only be called if the resolver has not yet been called
this.diagnostics.check(resolverStatus == null, "resolver_called");
@@ -247,9 +264,9 @@ public class Resolver {
int count = 0;
ResolverStatus status;
do {
- status = runResolvePass();
+ status = runResolvePass(plan);
count++;
- } while (status == null && ++count < 1000);
+ } while (status == null && count < 1000);
// assert if status is null
this.diagnostics.checkNotNull(status, "cannot_resolve_all_entities");
@@ -274,6 +291,7 @@ public class Resolver {
this.diagnostics.check(
resolverStatus.getStatus() == ResolverStatus.StatusEnum.SUCCESS,
"resolver_must_be_successful");
+ this.diagnostics.check(resolvedCallerPrincipal != null,
"caller_principal_not_resolved");
return resolvedCallerPrincipal;
}
@@ -287,7 +305,6 @@ public class Resolver {
this.diagnostics.check(
resolverStatus.getStatus() == ResolverStatus.StatusEnum.SUCCESS,
"resolver_must_be_successful");
-
return resolvedCallerPrincipalRoles;
}
@@ -382,6 +399,7 @@ public class Resolver {
if (entityType.isTopLevel()) {
return this.resolvedEntriesByName.get(new
EntityCacheByNameKey(entityType, entityName));
} else {
+ diagnostics.checkNotNull(resolvedReferenceCatalog,
"reference_catalog_not_resolved");
long catalogId = this.resolvedReferenceCatalog.getEntity().getId();
return this.resolvedEntriesByName.get(
new EntityCacheByNameKey(catalogId, catalogId, entityType,
entityName));
@@ -393,7 +411,7 @@ public class Resolver {
*
* @return status of the resolve pass
*/
- private ResolverStatus runResolvePass() {
+ private ResolverStatus runResolvePass(ResolvePlan plan) {
// we will resolve those again
this.resolvedCallerPrincipal = null;
@@ -409,23 +427,30 @@ public class Resolver {
List<ResolvedPolarisEntity> toValidate = new ArrayList<>();
// first resolve the principal and determine the set of activated
principal roles
- ResolverStatus status =
this.resolveCallerPrincipalAndPrincipalRoles(toValidate);
+ ResolverStatus status =
+ plan.resolveCallerPrincipal()
+ ? this.resolveCallerPrincipalAndPrincipalRoles(toValidate,
plan.resolvePrincipalRoles())
+ : new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS);
// if success, continue resolving
if (status.getStatus() == ResolverStatus.StatusEnum.SUCCESS) {
// then resolve the reference catalog if one was specified
- if (this.referenceCatalogName != null) {
- status = this.resolveReferenceCatalog(toValidate,
this.referenceCatalogName);
+ if (plan.resolveReferenceCatalog()) {
+ this.diagnostics.checkNotNull(this.referenceCatalogName,
"reference_catalog_expected");
+ status =
+ this.resolveReferenceCatalog(
+ toValidate, this.referenceCatalogName,
plan.resolveCatalogRoles());
}
// if success, continue resolving
if (status.getStatus() == ResolverStatus.StatusEnum.SUCCESS) {
// then resolve all the additional entities we were asked to resolve
- status = this.resolveEntities(toValidate, this.entitiesToResolve);
+ if (plan.resolveTopLevelEntities()) {
+ status = this.resolveEntities(toValidate, this.entitiesToResolve);
+ }
// if success, continue resolving
- if (status.getStatus() == ResolverStatus.StatusEnum.SUCCESS
- && this.referenceCatalogName != null) {
+ if (status.getStatus() == ResolverStatus.StatusEnum.SUCCESS &&
plan.resolvePaths()) {
// finally, resolve all paths we need to resolve
status = this.resolvePaths(toValidate, this.pathsToResolve);
}
@@ -679,7 +704,6 @@ public class Resolver {
*/
private ResolverStatus resolvePaths(
List<ResolvedPolarisEntity> toValidate, List<ResolverPath>
pathsToResolve) {
-
// id of the catalog for all these paths
final long catalogId = this.resolvedReferenceCatalog.getEntity().getId();
@@ -742,7 +766,7 @@ public class Resolver {
* @return the status of resolution
*/
private ResolverStatus resolveCallerPrincipalAndPrincipalRoles(
- List<ResolvedPolarisEntity> toValidate) {
+ List<ResolvedPolarisEntity> toValidate, boolean resolvePrincipalRoles) {
// resolve the principal, by name or id
this.resolvedCallerPrincipal =
@@ -755,10 +779,14 @@ public class Resolver {
}
// activate all principal roles specified in the authenticated principal
- resolvedCallerPrincipalRoles =
- this.polarisPrincipal.getRoles().isEmpty()
- ? resolveAllPrincipalRoles(toValidate, resolvedCallerPrincipal)
- : resolvePrincipalRolesByName(toValidate,
this.polarisPrincipal.getRoles());
+ if (resolvePrincipalRoles) {
+ resolvedCallerPrincipalRoles =
+ this.polarisPrincipal.getRoles().isEmpty()
+ ? resolveAllPrincipalRoles(toValidate, resolvedCallerPrincipal)
+ : resolvePrincipalRolesByName(toValidate,
this.polarisPrincipal.getRoles());
+ } else {
+ resolvedCallerPrincipalRoles = new ArrayList<>();
+ }
// total success
return new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS);
@@ -811,7 +839,9 @@ public class Resolver {
* @return the status of resolution
*/
private ResolverStatus resolveReferenceCatalog(
- @Nonnull List<ResolvedPolarisEntity> toValidate, @Nonnull String
referenceCatalogName) {
+ @Nonnull List<ResolvedPolarisEntity> toValidate,
+ @Nonnull String referenceCatalogName,
+ boolean resolveCatalogRoles) {
// resolve the catalog
this.resolvedReferenceCatalog =
this.resolveByName(toValidate, PolarisEntityType.CATALOG,
referenceCatalogName);
@@ -822,26 +852,28 @@ public class Resolver {
return new ResolverStatus(PolarisEntityType.CATALOG,
this.referenceCatalogName);
}
- // determine the set of catalog roles which have been activated
- long catalogId = this.resolvedReferenceCatalog.getEntity().getId();
- for (ResolvedPolarisEntity principalRole : resolvedCallerPrincipalRoles) {
- for (PolarisGrantRecord grantRecord :
principalRole.getGrantRecordsAsGrantee()) {
- // the securable is a catalog role belonging to
- if (grantRecord.getPrivilegeCode() ==
PolarisPrivilege.CATALOG_ROLE_USAGE.getCode()
- && grantRecord.getSecurableCatalogId() == catalogId) {
- // the id of the catalog role
- long catalogRoleId = grantRecord.getSecurableId();
-
- // skip if it has already been added
- if (!this.resolvedCatalogRoles.containsKey(catalogRoleId)) {
- // see if this catalog can be resolved
- ResolvedPolarisEntity catalogRole =
- this.resolveById(
- toValidate, PolarisEntityType.CATALOG_ROLE, catalogId,
catalogRoleId);
-
- // if found and not dropped, add it to the list of activated
catalog roles
- if (catalogRole != null && !catalogRole.getEntity().isDropped()) {
- this.resolvedCatalogRoles.put(catalogRoleId, catalogRole);
+ if (resolveCatalogRoles) {
+ // determine the set of catalog roles which have been activated
+ long catalogId = this.resolvedReferenceCatalog.getEntity().getId();
+ for (ResolvedPolarisEntity principalRole : resolvedCallerPrincipalRoles)
{
+ for (PolarisGrantRecord grantRecord :
principalRole.getGrantRecordsAsGrantee()) {
+ // the securable is a catalog role belonging to
+ if (grantRecord.getPrivilegeCode() ==
PolarisPrivilege.CATALOG_ROLE_USAGE.getCode()
+ && grantRecord.getSecurableCatalogId() == catalogId) {
+ // the id of the catalog role
+ long catalogRoleId = grantRecord.getSecurableId();
+
+ // skip if it has already been added
+ if (!this.resolvedCatalogRoles.containsKey(catalogRoleId)) {
+ // see if this catalog can be resolved
+ ResolvedPolarisEntity catalogRole =
+ this.resolveById(
+ toValidate, PolarisEntityType.CATALOG_ROLE, catalogId,
catalogRoleId);
+
+ // if found and not dropped, add it to the list of activated
catalog roles
+ if (catalogRole != null && !catalogRole.getEntity().isDropped())
{
+ this.resolvedCatalogRoles.put(catalogRoleId, catalogRole);
+ }
}
}
}
@@ -856,6 +888,53 @@ public class Resolver {
return new ResolverStatus(ResolverStatus.StatusEnum.SUCCESS);
}
+ private static record ResolvePlan(
+ boolean resolveCallerPrincipal,
+ boolean resolvePrincipalRoles,
+ boolean resolveReferenceCatalog,
+ boolean resolveCatalogRoles,
+ boolean resolveTopLevelEntities,
+ boolean resolvePaths) {
+
+ private static ResolvePlan all(@Nullable String referenceCatalogName) {
+ boolean hasReferenceCatalog = referenceCatalogName != null;
+ return new ResolvePlan(
+ true, true, hasReferenceCatalog, hasReferenceCatalog, true,
hasReferenceCatalog);
+ }
+
+ private static ResolvePlan fromSelections(
+ Set<Resolvable> selections, @Nullable String referenceCatalogName) {
+ boolean resolvePaths = selections.contains(Resolvable.REQUESTED_PATHS);
+ boolean resolveTopLevelEntities =
+ selections.contains(Resolvable.REQUESTED_TOP_LEVEL_ENTITIES);
+ boolean resolveCatalogRoles =
selections.contains(Resolvable.CATALOG_ROLES);
+ // Principal roles depend on resolving the caller principal, and catalog
roles depend on
+ // principal roles. Only those selections require caller principal
resolution.
+ boolean resolvePrincipalRoles =
+ selections.contains(Resolvable.CALLER_PRINCIPAL_ROLES) ||
resolveCatalogRoles;
+ // Reference catalog is required for path resolution and catalog role
expansion.
+ boolean resolveReferenceCatalog =
+ selections.contains(Resolvable.REFERENCE_CATALOG) || resolvePaths ||
resolveCatalogRoles;
+ boolean resolveCallerPrincipal =
+ selections.contains(Resolvable.CALLER_PRINCIPAL) ||
resolvePrincipalRoles;
+
+ boolean hasReferenceCatalog = referenceCatalogName != null;
+ if (!hasReferenceCatalog && resolveReferenceCatalog) {
+ throw new IllegalArgumentException(
+ "Reference-catalog-dependent selections were requested, but no
reference catalog name "
+ + "was provided");
+ }
+
+ return new ResolvePlan(
+ resolveCallerPrincipal,
+ resolvePrincipalRoles,
+ resolveReferenceCatalog,
+ resolveCatalogRoles,
+ resolveTopLevelEntities,
+ resolvePaths);
+ }
+ }
+
/**
* Add a resolved entity to the current resolution collection's set of
resolved entities
*
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationRequestTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationRequestTest.java
new file mode 100644
index 0000000000..b46cb906a1
--- /dev/null
+++
b/polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationRequestTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.polaris.core.auth;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.junit.jupiter.api.Test;
+
+public class AuthorizationRequestTest {
+
+ @Test
+ void hasSecurableTypeReturnsTrueForPrincipalTarget() {
+ AuthorizationRequest request =
+ AuthorizationRequest.of(
+ PolarisPrincipal.of("alice", Map.of(), Set.of("role")),
+ PolarisAuthorizableOperation.LOAD_TABLE,
+ List.of(
+ AuthorizationTargetBinding.of(
+ PolarisSecurable.of(PolarisEntityType.PRINCIPAL,
List.of("alice")), null)));
+
+ assertThat(request.hasSecurableType(PolarisEntityType.PRINCIPAL)).isTrue();
+ }
+
+ @Test
+ void hasSecurableTypeReturnsTrueForPrincipalRoleSecondary() {
+ AuthorizationRequest request =
+ AuthorizationRequest.of(
+ PolarisPrincipal.of("alice", Map.of(), Set.of("role")),
+ PolarisAuthorizableOperation.ASSIGN_PRINCIPAL_ROLE,
+ List.of(
+ AuthorizationTargetBinding.of(
+ PolarisSecurable.of(PolarisEntityType.PRINCIPAL,
List.of("alice")),
+ PolarisSecurable.of(
+ PolarisEntityType.PRINCIPAL_ROLE,
List.of("analytics-admin")))));
+
+
assertThat(request.hasSecurableType(PolarisEntityType.PRINCIPAL_ROLE)).isTrue();
+ }
+
+ @Test
+ void hasSecurableTypeReturnsTrueForCatalogRoleAcrossMultipleBindings() {
+ AuthorizationRequest request =
+ AuthorizationRequest.of(
+ PolarisPrincipal.of("alice", Map.of(), Set.of("role")),
+ PolarisAuthorizableOperation.ASSIGN_CATALOG_ROLE_TO_PRINCIPAL_ROLE,
+ List.of(
+ AuthorizationTargetBinding.of(
+ PolarisSecurable.of(PolarisEntityType.NAMESPACE,
List.of("catalog", "ns")),
+ null),
+ AuthorizationTargetBinding.of(
+ PolarisSecurable.of(PolarisEntityType.CATALOG,
List.of("catalog")),
+ PolarisSecurable.of(PolarisEntityType.CATALOG_ROLE,
List.of("catalog-role")))));
+
+
assertThat(request.hasSecurableType(PolarisEntityType.CATALOG_ROLE)).isTrue();
+ }
+
+ @Test
+ void hasSecurableTypeReturnsFalseWhenTypeAbsent() {
+ AuthorizationRequest request =
+ AuthorizationRequest.of(
+ PolarisPrincipal.of("alice", Map.of(), Set.of("role")),
+ PolarisAuthorizableOperation.LOAD_VIEW,
+ List.of(
+ AuthorizationTargetBinding.of(
+ PolarisSecurable.of(PolarisEntityType.CATALOG,
List.of("catalog")), null)));
+
+
assertThat(request.hasSecurableType(PolarisEntityType.PRINCIPAL_ROLE)).isFalse();
+ }
+}
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
index 3c8633a827..3af68107a2 100644
---
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
@@ -21,10 +21,21 @@ package org.apache.polaris.core.persistence;
import static
org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RANDOM_SECRETS;
import java.time.Clock;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.core.persistence.resolver.Resolvable;
+import org.apache.polaris.core.persistence.resolver.Resolver;
+import org.apache.polaris.core.persistence.resolver.ResolverPath;
+import org.apache.polaris.core.persistence.resolver.ResolverStatus;
import
org.apache.polaris.core.persistence.transactional.TransactionalMetaStoreManagerImpl;
import org.apache.polaris.core.persistence.transactional.TreeMapMetaStore;
import
org.apache.polaris.core.persistence.transactional.TreeMapTransactionalPersistenceImpl;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
public class ResolverTest extends BaseResolverTest {
@@ -62,4 +73,111 @@ public class ResolverTest extends BaseResolverTest {
}
return tm;
}
+
+ @Test
+ public void testResolveSelectionsSkipsCallerPrincipalForReferenceCatalog() {
+ Resolver resolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ "test");
+ ResolverStatus status =
resolver.resolveSelections(Set.of(Resolvable.REFERENCE_CATALOG));
+
Assertions.assertThat(status.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS);
+ }
+
+ @Test
+ public void testResolveSelectionsSkipsCallerPrincipalForRequestedPaths() {
+ Resolver pathResolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ "test");
+ pathResolver.addPath(new ResolverPath(List.of("N1"),
PolarisEntityType.NAMESPACE));
+ ResolverStatus pathStatus =
pathResolver.resolveSelections(Set.of(Resolvable.REQUESTED_PATHS));
+
Assertions.assertThat(pathStatus.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS);
+ }
+
+ @Test
+ public void
testResolveSelectionsSkipsCallerPrincipalForRequestedTopLevelEntities() {
+ Resolver entityResolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ null);
+ entityResolver.addEntityByName(PolarisEntityType.PRINCIPAL, "P1");
+ ResolverStatus entityStatus =
+
entityResolver.resolveSelections(Set.of(Resolvable.REQUESTED_TOP_LEVEL_ENTITIES));
+
Assertions.assertThat(entityStatus.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS);
+ }
+
+ @Test
+ public void testResolveSelectionsRequiresCallerPrincipalForCatalogRoles() {
+ Resolver resolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ "test");
+ ResolverStatus status =
resolver.resolveSelections(Set.of(Resolvable.CATALOG_ROLES));
+ Assertions.assertThat(status.getStatus())
+ .isEqualTo(ResolverStatus.StatusEnum.CALLER_PRINCIPAL_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void testResolveSelectionsRequiresCallerPrincipalForCallerPrincipal()
{
+ Resolver resolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ "test");
+ ResolverStatus status =
resolver.resolveSelections(Set.of(Resolvable.CALLER_PRINCIPAL));
+ Assertions.assertThat(status.getStatus())
+ .isEqualTo(ResolverStatus.StatusEnum.CALLER_PRINCIPAL_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void
testResolveSelectionsRequiresCallerPrincipalForCallerPrincipalRoles() {
+ Resolver resolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ "test");
+ ResolverStatus status =
resolver.resolveSelections(Set.of(Resolvable.CALLER_PRINCIPAL_ROLES));
+ Assertions.assertThat(status.getStatus())
+ .isEqualTo(ResolverStatus.StatusEnum.CALLER_PRINCIPAL_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void testResolveSelectionsThrowsOnGetResolvedCallerPrincipal() {
+ Resolver resolver =
+ new Resolver(
+ diagServices,
+ callCtx(),
+ metaStoreManager(),
+ PolarisPrincipal.of("missing", Map.of(), Set.of()),
+ null,
+ "test");
+ ResolverStatus status =
resolver.resolveSelections(Set.of(Resolvable.REFERENCE_CATALOG));
+
Assertions.assertThat(status.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS);
+ Assertions.assertThatThrownBy(resolver::getResolvedCallerPrincipal)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("caller_principal_not_resolved");
+ }
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
index 8f906c6f99..688243171b 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.auth.AuthorizationState;
import org.apache.polaris.core.auth.DefaultPolarisAuthorizerFactory;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.auth.PolarisAuthorizerFactory;
@@ -164,6 +165,12 @@ public class ServiceProducers {
return factory.create(realmConfig);
}
+ @Produces
+ @RequestScoped
+ public AuthorizationState authorizationState() {
+ return new AuthorizationState();
+ }
+
@Produces
@RequestScoped
public ResolverFactory resolverFactory(