Skip to content

Commit

Permalink
Make Authorizer pluggable (#8090)
Browse files Browse the repository at this point in the history
  • Loading branch information
snazy committed Feb 21, 2024
1 parent c74626b commit 97e0dc7
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023 Dremio
* Copyright (C) 2024 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,32 +15,25 @@
*/
package org.projectnessie.server.authz;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
import org.projectnessie.server.config.QuarkusNessieAuthorizationConfig;
import org.projectnessie.services.authz.AbstractBatchAccessChecker;
import org.projectnessie.services.authz.AccessContext;
import org.projectnessie.services.authz.Authorizer;
import org.projectnessie.services.authz.AuthorizerType;
import org.projectnessie.services.authz.BatchAccessChecker;

@ApplicationScoped
@AuthorizerType("CEL")
@Dependent
public class CelAuthorizer implements Authorizer {
private final QuarkusNessieAuthorizationConfig config;
private final CompiledAuthorizationRules compiledRules;

@Inject
public CelAuthorizer(
QuarkusNessieAuthorizationConfig config, CompiledAuthorizationRules compiledRules) {
this.config = config;
public CelAuthorizer(CompiledAuthorizationRules compiledRules) {
this.compiledRules = compiledRules;
}

@Override
public BatchAccessChecker startAccessCheck(AccessContext context) {
if (!config.enabled()) {
return AbstractBatchAccessChecker.NOOP_ACCESS_CHECKER;
}

return new CelBatchAccessChecker(compiledRules, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2023 Dremio
*
* 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.projectnessie.server.authz;

import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import org.projectnessie.server.config.QuarkusNessieAuthorizationConfig;
import org.projectnessie.services.authz.AbstractBatchAccessChecker;
import org.projectnessie.services.authz.AccessContext;
import org.projectnessie.services.authz.Authorizer;
import org.projectnessie.services.authz.AuthorizerType;
import org.projectnessie.services.authz.BatchAccessChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ApplicationScoped
@Startup
public class QuarkusAuthorizer implements Authorizer {
private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusAuthorizer.class);
private final Authorizer authorizer;

@Inject
public QuarkusAuthorizer(
QuarkusNessieAuthorizationConfig config, @Any Instance<Authorizer> authorizers) {
if (config.enabled()) {
if (authorizers.isUnsatisfied()) {
throw new IllegalStateException("No Nessie Authorizer available");
}
Instance<Authorizer> authorizerInstance =
authorizers.select(new AuthorizerType.Literal(config.authorizationType()));
if (authorizerInstance.isUnsatisfied()) {
throw new IllegalStateException(
"No Nessie Authorizer of type '" + config.authorizationType() + "' available");
}

LOGGER.info("Using Nessie {} Authorizer", config.authorizationType());

this.authorizer = authorizerInstance.get();
} else {
this.authorizer = context -> AbstractBatchAccessChecker.NOOP_ACCESS_CHECKER;
}
}

@Override
public BatchAccessChecker startAccessCheck(AccessContext context) {
return this.authorizer.startAccessCheck(context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public interface QuarkusNessieAuthorizationConfig {
@WithDefault("false")
boolean enabled();

@WithName("type")
@WithDefault("CEL")
String authorizationType();

/**
* The authorization rules where the key represents the rule id and the value the CEL expression.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
*/
package org.projectnessie.server.authz;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.projectnessie.services.authz.Check.CheckType.CREATE_REFERENCE;
import static org.projectnessie.services.authz.Check.CheckType.VIEW_REFERENCE;

import jakarta.enterprise.inject.Instance;
import java.util.Collections;
import java.util.Map;
import org.assertj.core.api.SoftAssertions;
Expand All @@ -32,6 +35,8 @@
import org.projectnessie.server.config.QuarkusNessieAuthorizationConfig;
import org.projectnessie.services.authz.AbstractBatchAccessChecker;
import org.projectnessie.services.authz.AccessCheckException;
import org.projectnessie.services.authz.Authorizer;
import org.projectnessie.services.authz.AuthorizerType;
import org.projectnessie.services.authz.Check;
import org.projectnessie.services.authz.Check.CheckType;
import org.projectnessie.services.authz.ServerAccessContext;
Expand Down Expand Up @@ -106,30 +111,42 @@ void celAuthorizer() {
QuarkusNessieAuthorizationConfig configDisabled = buildConfig(false);

CompiledAuthorizationRules rules = new CompiledAuthorizationRules(configEnabled);
CelAuthorizer celAuthorizer = new CelAuthorizer(rules);

Instance<Authorizer> authorizers = mock(Instance.class);
Instance<Authorizer> celAuthorizerInstance = mock(Instance.class);

when(celAuthorizerInstance.get()).thenReturn(celAuthorizer);
when(authorizers.select(new AuthorizerType.Literal("CEL"))).thenReturn(celAuthorizerInstance);
soft.assertThat(
new CelAuthorizer(configEnabled, rules)
new QuarkusAuthorizer(configEnabled, authorizers)
.startAccessCheck(ServerAccessContext.of("meep", () -> "some-user")))
.isInstanceOf(CelBatchAccessChecker.class);

when(celAuthorizerInstance.get()).thenReturn(celAuthorizer);
when(authorizers.select(new AuthorizerType.Literal("CEL"))).thenReturn(celAuthorizerInstance);
soft.assertThat(
new CelAuthorizer(configDisabled, rules)
new QuarkusAuthorizer(configDisabled, authorizers)
.startAccessCheck(ServerAccessContext.of("meep", () -> "some-user")))
.isSameAs(AbstractBatchAccessChecker.NOOP_ACCESS_CHECKER);
}

private static QuarkusNessieAuthorizationConfig buildConfig(boolean enabled) {
QuarkusNessieAuthorizationConfig config =
new QuarkusNessieAuthorizationConfig() {
@Override
public boolean enabled() {
return enabled;
}

@Override
public Map<String, String> rules() {
return Collections.singletonMap("foo", "false");
}
};
return config;
return new QuarkusNessieAuthorizationConfig() {
@Override
public String authorizationType() {
return "CEL";
}

@Override
public boolean enabled() {
return enabled;
}

@Override
public Map<String, String> rules() {
return Collections.singletonMap("foo", "false");
}
};
}
}
2 changes: 2 additions & 0 deletions servers/services/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dependencies {

compileOnly(libs.jakarta.validation.api)
compileOnly(libs.jakarta.annotation.api)
compileOnly(libs.jakarta.inject.api)
compileOnly(libs.jakarta.enterprise.cdi.api)

compileOnly(platform(libs.jackson.bom))
compileOnly("com.fasterxml.jackson.core:jackson-annotations")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 Dremio
*
* 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.projectnessie.services.authz;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
@Qualifier
public @interface AuthorizerType {
/** Gets the store type. */
String value();

/** Supports inline instantiation of the {@link AuthorizerType} qualifier. */
final class Literal extends AnnotationLiteral<AuthorizerType> implements AuthorizerType {

private static final long serialVersionUID = 1L;
private final String value;

public Literal(String value) {
this.value = value;
}

@Override
public String value() {
return value;
}
}
}

0 comments on commit 97e0dc7

Please sign in to comment.