Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Replace Keto Authorization with External HTTP Authorization #864

Merged
merged 11 commits into from
Jul 10, 2020
101 changes: 95 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,89 @@
<artifactId>hibernate-validator</artifactId>
<version>6.1.2.Final</version>
</dependency>
</dependencies>
<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>
<!-- @Nullable annotation -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</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>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3-version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-version}</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>
</plugins>
</build>
</project>
72 changes: 72 additions & 0 deletions auth/src/main/java/feast/auth/authorization/AuthUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import java.util.Map;
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;

public class AuthUtil {

/**
* Get user email from their authentication object.
*
* @param authentication Spring Security Authentication object, used to extract user details
* @return String user email
*/
public static String getEmailFromAuth(Authentication authentication) {
Jwt principle = ((Jwt) authentication.getPrincipal());
Map<String, Object> claims = principle.getClaims();
String email = (String) claims.get("email");

if (email.isEmpty()) {
throw new IllegalStateException("JWT does not have a valid email set.");
}
boolean validEmail = (new EmailValidator()).isValid(email, null);
if (!validEmail) {
throw new IllegalStateException("JWT contains an invalid email address");
}
return email;
}

/**
* Converts Spring Authentication object into Json String form.
*
* @param authentication Authentication object that contains request level authentication metadata
* @return Json representation of authentication object
*/
public static String authenticationToJson(Authentication authentication) {
ObjectWriter ow =
new ObjectMapper()
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.writer()
.withDefaultPrettyPrinter();
try {
return ow.writeValueAsString(authentication);
} catch (JsonProcessingException e) {
throw new RuntimeException(
String.format(
"Could not convert Authentication object to JSON: %s", authentication.toString()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 static feast.auth.authorization.AuthUtil.getEmailFromAuth;

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.CheckProjectAccessRequest;
import feast.auth.generated.client.model.CheckProjectAccessResponse;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;

/** Authorization Provider implementation for external HTTP authorization server */
public class HTTPAuthorizationProvider implements AuthorizationProvider {

private static final Logger log = LoggerFactory.getLogger(HTTPAuthorizationProvider.class);
private final DefaultApi defaultApiClient;

/**
* 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("externalAuthUrl"));
this.defaultApiClient = new DefaultApi(apiClient);
}

/**
* Validates whether a user has access to the project
*
* @param project Name of the Feast project
* @param authentication Spring Security Authentication object
* @return AuthorizationResult result of authorization query
*/
public AuthorizationResult checkAccess(String project, Authentication authentication) {
String email = getEmailFromAuth(authentication);
CheckProjectAccessRequest checkProjectAccessRequest =
new CheckProjectAccessRequest().project(project).authentication(authentication);

try {
// Make authorization request to external service
CheckProjectAccessResponse response =
defaultApiClient.checkProjectAccessPost(checkProjectAccessRequest);
if (response == null || response.getAllowed() == null) {
throw new RuntimeException(
String.format(
"Empty response returned for HTTP authorization, email %s, authentication %s",
email, authentication.toString()));
}
if (response.getAllowed()) {
// Successfully authenticated
return AuthorizationResult.success();
}
// Could not determine project membership, deny access.
return AuthorizationResult.failed(
String.format(
"Access denied to project %s for user %s with message %s",
project, email, response.getMessage()));
} catch (ApiException e) {
log.error("API exception has occurred while authenticating user: {}", e.getMessage(), e);
}

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

This file was deleted.

Loading