Skip to content

Commit

Permalink
Replace Keto Authorization with External HTTP Authorization (#864)
Browse files Browse the repository at this point in the history
* Replace Keto Auth with external HTTP Auth

* Update dependencies for Auth Java client in Auth module

* Allow JavaDoc linting to be disabled for Auth module

* Add exclusion for DefaultAPI that causes failure in javadoc lint

* Remove AuthUtil

* Clean up dependencies

* Improve class for comment for HTTPAuthorizationProvider

* Fix casing for HTTPAuthProvider

* Update OpenAPI specification to support resources for auth

* Update External HTTP Authorization Provider to use a more generalized contract

* Update formatting
  • Loading branch information
woop authored Jul 10, 2020
1 parent bc7f3fb commit dc159e4
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 126 deletions.
97 changes: 91 additions & 6 deletions auth/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@
<artifactId>feast-parent</artifactId>
<version>${revision}</version>
</parent>

<artifactId>feast-auth</artifactId>

<name>Feast Authentication and Authorization</name>

<properties>
<external.auth.client.package.name>feast.auth.generated.client</external.auth.client.package.name>
<gson-fire-version>1.8.4</gson-fire-version>
<swagger-core-version>1.5.24</swagger-core-version>
<okhttp-version>3.14.7</okhttp-version>
<gson-version>2.8.6</gson-version>
<commons-lang3-version>3.10</commons-lang3-version>
<javax-annotation-version>1.3.2</javax-annotation-version>
<junit-version>4.13</junit-version>
</properties>
<dependencies>
<dependency>
<groupId>dev.feast</groupId>
Expand All @@ -32,11 +43,6 @@
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>sh.ory.keto</groupId>
<artifactId>keto-client</artifactId>
<version>0.4.4-alpha.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand All @@ -46,6 +52,85 @@
<artifactId>hibernate-validator</artifactId>
<version>6.1.2.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-core-version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp-version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp-version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson-version}</version>
</dependency>
<dependency>
<groupId>io.gsonfire</groupId>
<artifactId>gson-fire</artifactId>
<version>${gson-fire-version}</version>
</dependency>
<!-- @Nullable annotation -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>4.3.1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
<generatorName>java</generatorName>
<packageName>${external.auth.client.package.name}</packageName>
<modelPackage>${external.auth.client.package.name}.model</modelPackage>
<apiPackage>${external.auth.client.package.name}.api</apiPackage>
<invokerPackage>${external.auth.client.package.name}.invoker</invokerPackage>
<configOptions>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<artifactVersion>${project.version}</artifactVersion>
<java8>true</java8>
<dateLibrary>java8</dateLibrary>
<licenseName>Apache 2.0</licenseName>
<licenseUrl>https://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
<output>${project.build.directory}/generated-sources</output>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<excludePackageNames>feast.auth.generated.client.api</excludePackageNames>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
public interface AuthorizationProvider {

/**
* Validates whether a user is allowed access to the project
* Validates whether a user is allowed access to a project
*
* @param project Name of the Feast project
* @param projectId Id of the Feast project
* @param authentication Spring Security Authentication object
* @return AuthorizationResult result of authorization query
*/
AuthorizationResult checkAccess(String project, Authentication authentication);
AuthorizationResult checkAccessToProject(String projectId, Authentication authentication);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2018-2020 The Feast 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
*
* https://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 feast.auth.authorization;

import feast.auth.generated.client.api.DefaultApi;
import feast.auth.generated.client.invoker.ApiClient;
import feast.auth.generated.client.invoker.ApiException;
import feast.auth.generated.client.model.CheckAccessRequest;
import java.util.Map;
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;

/**
* HTTPAuthorizationProvider uses an external HTTP service for authorizing requests. Please see
* auth/src/main/resources/api.yaml for the API specification of this external service.
*/
public class HttpAuthorizationProvider implements AuthorizationProvider {

private static final Logger log = LoggerFactory.getLogger(HttpAuthorizationProvider.class);

private final DefaultApi defaultApiClient;

/**
* The default subject claim is the key within the Authentication object where the user's identity
* can be found
*/
private final String DEFAULT_SUBJECT_CLAIM = "email";

/**
* Initializes the HTTPAuthorizationProvider
*
* @param options String K/V pair of options to initialize the provider with. Expects at least a
* "basePath" for the provider URL
*/
public HttpAuthorizationProvider(Map<String, String> options) {
if (options == null) {
throw new IllegalArgumentException(
"Cannot pass empty or null options to HTTPAuthorizationProvider");
}

ApiClient apiClient = new ApiClient();
apiClient.setBasePath(options.get("authorizationUrl"));
this.defaultApiClient = new DefaultApi(apiClient);
}

/**
* Validates whether a user has access to a project
*
* @param projectId Name of the Feast project
* @param authentication Spring Security Authentication object
* @return AuthorizationResult result of authorization query
*/
public AuthorizationResult checkAccessToProject(String projectId, Authentication authentication) {

CheckAccessRequest checkAccessRequest = new CheckAccessRequest();
Object context = getContext(authentication);
String subject = getSubjectFromAuth(authentication, DEFAULT_SUBJECT_CLAIM);
checkAccessRequest.setAction("ALL");
checkAccessRequest.setContext(context);
checkAccessRequest.setResource(projectId);
checkAccessRequest.setSubject(subject);

try {
// Make authorization request to external service
feast.auth.generated.client.model.AuthorizationResult authResult =
defaultApiClient.checkAccessPost(checkAccessRequest);
if (authResult == null) {
throw new RuntimeException(
String.format(
"Empty response returned for access to project %s for subject %s",
projectId, subject));
}
if (authResult.getAllowed()) {
// Successfully authenticated
return AuthorizationResult.success();
}
} catch (ApiException e) {
log.error("API exception has occurred during authorization: {}", e.getMessage(), e);
}

// Could not determine project membership, deny access.
return AuthorizationResult.failed(
String.format("Access denied to project %s for subject %s", projectId, subject));
}

/**
* Extract a context object to send as metadata to the authorization service
*
* @param authentication Spring Security Authentication object
* @return Returns a context object that will be serialized and sent as metadata to the
* authorization service
*/
private Object getContext(Authentication authentication) {
// Not implemented yet, left empty
return new Object();
}

/**
* Get user email from their authentication object.
*
* @param authentication Spring Security Authentication object, used to extract user details
* @param subjectClaim Indicates the claim where the subject can be found
* @return String user email
*/
private String getSubjectFromAuth(Authentication authentication, String subjectClaim) {
Jwt principle = ((Jwt) authentication.getPrincipal());
Map<String, Object> claims = principle.getClaims();
String subjectValue = (String) claims.get(subjectClaim);

if (subjectValue.isEmpty()) {
throw new IllegalStateException(
String.format("JWT does not have a valid claim %s.", subjectClaim));
}

if (subjectClaim.equals("email")) {
boolean validEmail = (new EmailValidator()).isValid(subjectValue, null);
if (!validEmail) {
throw new IllegalStateException("JWT contains an invalid email address");
}
}

return subjectValue;
}
}

This file was deleted.

Loading

0 comments on commit dc159e4

Please # to comment.