diff options
Diffstat (limited to 'sdnr/wt/oauth-provider/provider-jar')
25 files changed, 3247 insertions, 0 deletions
diff --git a/sdnr/wt/oauth-provider/provider-jar/pom.xml b/sdnr/wt/oauth-provider/provider-jar/pom.xml new file mode 100644 index 000000000..3ee21b2aa --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/pom.xml @@ -0,0 +1,178 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ ============LICENSE_START======================================================= + ~ ONAP : ccsdk features + ~ ================================================================================ + ~ Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. + ~ All rights reserved. + ~ ================================================================================ + ~ Update Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + ~ ================================================================================ + ~ 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. + ~ ============LICENSE_END======================================================= + ~ + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.ccsdk.parent</groupId> + <artifactId>binding-parent</artifactId> + <version>2.1.1-SNAPSHOT</version> + <relativePath/> + </parent> + + <groupId>org.onap.ccsdk.features.sdnr.wt</groupId> + <artifactId>sdnr-wt-oauth-provider-jar</artifactId> + <version>1.1.1-SNAPSHOT</version> + <packaging>jar</packaging> + + <name>ccsdk-features :: ${project.artifactId}</name> + <licenses> + <license> + <name>Apache License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0</url> + </license> + </licenses> + + <properties> + <maven.javadoc.skip>true</maven.javadoc.skip> + <checkstyle.skip>true</checkstyle.skip> + </properties> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.apache.shiro</groupId> + <artifactId>shiro-core</artifactId> + <version>1.3.2</version> + </dependency> + </dependencies> + </dependencyManagement> + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>com.auth0</groupId> + <artifactId>java-jwt</artifactId> + <version>3.11.0</version> + <exclusions> + <exclusion> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </exclusion> + <exclusion> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>com.highstreet-technologies.aaa</groupId> + <artifactId>aaa-shiro</artifactId> + <version>0.12.1</version> + <exclusions> +<!-- <exclusion> --> +<!-- <groupId>org.opendaylight.aaa</groupId> --> +<!-- <artifactId>aaa-shiro-api</artifactId> --> +<!-- </exclusion> --> + <exclusion> + <groupId>org.opendaylight.aaa</groupId> + <artifactId>aaa-cert</artifactId> + </exclusion> + <exclusion> + <groupId>org.opendaylight.aaa</groupId> + <artifactId>aaa-filterchain</artifactId> + </exclusion> + <exclusion> + <groupId>org.opendaylight.aaa</groupId> + <artifactId>aaa-password-service-api</artifactId> + </exclusion> + <exclusion> + <groupId>org.opendaylight.aaa.web</groupId> + <artifactId>web-api</artifactId> + </exclusion> + <exclusion> + <groupId>org.opendaylight.aaa.web</groupId> + <artifactId>servlet-api</artifactId> + </exclusion> + <exclusion> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + </exclusion> + <exclusion> + <groupId>commons-text</groupId> + <artifactId>commons-text</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </exclusion> + <exclusion> + <groupId>org.opendaylight.mdsal</groupId> + <artifactId>mdsal-binding-api</artifactId> + </exclusion> + <exclusion> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + </exclusion> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>ehcache-root</groupId> + <artifactId>ehcache</artifactId> + </exclusion> +<!-- <exclusion> --> +<!-- <groupId>org.jolokia</groupId> --> +<!-- <artifactId>jolokia-osgi</artifactId> --> +<!-- </exclusion> --> + </exclusions> + </dependency> + <dependency> + <groupId>org.opendaylight.mdsal</groupId> + <artifactId>yang-binding</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/OAuth2Realm.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/OAuth2Realm.java new file mode 100644 index 000000000..a3e4bcc2a --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/OAuth2Realm.java @@ -0,0 +1,143 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider; + +import com.auth0.jwt.interfaces.DecodedJWT; +import java.io.IOException; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.subject.PrincipalCollection; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator; +import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; +import org.opendaylight.aaa.shiro.realm.TokenAuthRealm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OAuth2Realm extends TokenAuthRealm { + + public static final String REALM_NAME = "OAuth2Realm"; + private static final Logger LOG = LoggerFactory.getLogger(OAuth2Realm.class); + private final TokenCreator tokenCreator; + private final Config config; + + public OAuth2Realm() throws IOException { + super(); + super.setName(REALM_NAME); + this.config = Config.getInstance(); + this.tokenCreator = TokenCreator.getInstance(); + LOG.info("instantiated"); + + } + + @Override + public boolean supports(AuthenticationToken token) { + boolean supports = (token instanceof BearerToken) + || (this.config.doSupportOdlUsers() && (token instanceof UsernamePasswordToken)); + LOG.debug("supports {} is {}", token == null ? null : token.getClass().getName(), supports); + return supports; + } + + @Override + public String getName() { + return REALM_NAME; + } + + @Override + protected void assertCredentialsMatch(AuthenticationToken atoken, AuthenticationInfo ai) + throws AuthenticationException { + LOG.debug("assertCredentialsMatch"); + if (atoken instanceof BearerToken) { + if (this.tokenCreator.verify(((BearerToken) atoken).getToken()) == null) { + throw new AuthenticationException("Credentials do not match"); + } + } else if (this.config.doSupportOdlUsers() && (atoken instanceof UsernamePasswordToken)) { + //nothing to do + } else { + throw new AuthenticationException("AuthenticationInfo is not an OAuth2AuthenticationInfo"); + } + } + + + // check what I can do + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg) { + + LOG.debug("auth info in shiro"); + Object principal = arg.getPrimaryPrincipal(); + if (principal instanceof DecodedJWT) { + LOG.debug("detected jwt token"); + try { + DecodedJWT token = (DecodedJWT) arg.getPrimaryPrincipal(); + String[] roles = token.getClaim("roles").asArray(String.class); + SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); + for (String role : roles) { + LOG.trace("found role {} in token", role); + authorizationInfo.addRole(role); + } + return authorizationInfo; + } catch (ClassCastException e) { + LOG.error("Couldn't decode authorization request", e); + } + } else if (principal instanceof ODLPrincipal) { + LOG.debug("detected basic token"); + ODLPrincipal odlPrincipal = (ODLPrincipal) principal; + return new SimpleAuthorizationInfo(odlPrincipal.getRoles()); + } + return new SimpleAuthorizationInfo(); + } + + + + // check who I am + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + + LOG.debug("auth token in shiro:"); + if (token instanceof UsernamePasswordToken && this.config.doSupportOdlUsers()) { + LOG.debug("basic auth token found"); + return super.doGetAuthenticationInfo(token); + } else if (token instanceof BearerToken) { + LOG.debug("jwt token found"); + BearerToken oauthToken = (BearerToken) token; + + DecodedJWT jwt = this.tokenCreator.verify(oauthToken.getToken()); + if (jwt != null) { + SimpleAuthenticationInfo authenticationInfo = + new SimpleAuthenticationInfo(jwt, token.getCredentials(), getName()); + return authenticationInfo; + + } + } else { + LOG.debug("no valid token found"); + } + throw new AuthenticationException("unable to verify token " + token); + + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/Config.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/Config.java new file mode 100644 index 000000000..ba26106c9 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/Config.java @@ -0,0 +1,238 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Config { + + private static final Logger LOG = LoggerFactory.getLogger(Config.class); + private static final String DEFAULT_CONFIGFILENAME = "etc/oauth-provider.config.json"; + private static final String ENVVARIABLE = "${"; + private static final String REGEXENVVARIABLE = "(\\$\\{[A-Z0-9_-]+\\})"; + private static final Pattern pattern = Pattern.compile(REGEXENVVARIABLE); + private static final String DEFAULT_TOKENISSUER = "Opendaylight"; + private static final String DEFAULT_TOKENSECRET = generateSecret(); + private static final String DEFAULT_REDIRECTURI = "/odlux/index.html#/oauth?token="; + private static final String DEFAULT_SUPPORTODLUSERS = "true"; + private static Config _instance; + + + private List<OAuthProviderConfig> providers; + private String host; + private String redirectUri; + private String supportOdlUsers; + private String tokenSecret; + private String tokenIssuer; + + + @Override + public String toString() { + return "Config [providers=" + providers + ", host=" + host + ", redirectUri=" + redirectUri + + ", supportOdlUsers=" + supportOdlUsers + ", tokenSecret=" + tokenSecret + ", tokenIssuer=" + + tokenIssuer + "]"; + } + + + + public List<OAuthProviderConfig> getProviders() { + return providers; + } + + public void setProviders(List<OAuthProviderConfig> providers) { + this.providers = providers; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public String getSupportOdlUsers() { + return supportOdlUsers; + } + + public void setSupportOdlUsers(String supportOdlUsers) { + this.supportOdlUsers = supportOdlUsers; + } + + public String getTokenSecret() { + return tokenSecret; + } + + public void setTokenSecret(String tokenSecret) { + this.tokenSecret = tokenSecret; + } + + public String getTokenIssuer() { + return tokenIssuer; + } + + public void setTokenIssuer(String tokenIssuer) { + this.tokenIssuer = tokenIssuer; + } + + @JsonIgnore + private void handleEnvironmentVars() { + if (isEnvExpression(tokenIssuer)) { + this.tokenIssuer = getProperty(tokenIssuer, null); + } + if (isEnvExpression(tokenSecret)) { + this.tokenSecret = getProperty(tokenSecret, null); + } + if (isEnvExpression(host)) { + this.host = getProperty(host, null); + } + if (isEnvExpression(redirectUri)) { + this.redirectUri = getProperty(redirectUri, null); + } + if (isEnvExpression(supportOdlUsers)) { + this.supportOdlUsers = getProperty(supportOdlUsers, null); + } + } + + @JsonIgnore + private void handleDefaultValues() { + if (tokenIssuer == null || tokenIssuer.isEmpty()) { + this.tokenIssuer = DEFAULT_TOKENISSUER; + } + if (tokenSecret == null || tokenSecret.isEmpty()) { + this.tokenSecret = DEFAULT_TOKENSECRET; + } + if (redirectUri == null || redirectUri.isEmpty()) { + this.redirectUri = DEFAULT_REDIRECTURI; + } + if (supportOdlUsers == null || supportOdlUsers.isEmpty()) { + this.supportOdlUsers = DEFAULT_SUPPORTODLUSERS; + } + } + + static boolean isEnvExpression(String key) { + return key != null && key.contains(ENVVARIABLE); + } + + private static String generateSecret() { + int leftLimit = 48; // numeral '0' + int rightLimit = 122; // letter 'z' + int targetStringLength = 30; + Random random = new Random(); + + String generatedString = random.ints(leftLimit, rightLimit + 1) + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)).limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString(); + return generatedString; + } + + /** + * + * @param key environment var + * @param defValue default value if no env var found + * @return + */ + public static String getProperty(final String key, final String defValue) { + String value = defValue; + //try to read env var + boolean found = false; + if (isEnvExpression(key)) { + + LOG.info("try to find env var(s) for {}", key); + final Matcher matcher = pattern.matcher(key); + String tmp = new String(key); + while (matcher.find() && matcher.groupCount() > 0) { + final String mkey = matcher.group(1); + if (mkey != null) { + try { + LOG.info("match found for v={} and env key={}", key, mkey); + String envvar = mkey.substring(2, mkey.length() - 1); + String env = System.getenv(envvar); + tmp = tmp.replace(mkey, env == null ? "" : env); + if (env != null && env != "") { + found = true; + } + } catch (SecurityException e) { + LOG.warn("unable to read env {}: {}", key, e); + } + } + } + if (found) { + value = tmp; + } + } + return value; + } + + public static boolean getPropertyBoolean(String key, boolean defaultValue) { + final String value = getProperty(key, String.valueOf(defaultValue)); + return value.equals("true"); + } + + public static Config load(String filename) throws IOException { + CustomObjectMapper mapper = new CustomObjectMapper(); + File file = new File(filename); + if (!file.exists()) { + throw new FileNotFoundException(); + } + String content = String.join("", Files.readAllLines(file.toPath())); + Config cfg = mapper.readValue(content, Config.class); + cfg.handleEnvironmentVars(); + cfg.handleDefaultValues(); + return cfg; + } + + + @JsonIgnore + public boolean doSupportOdlUsers() { + return "true".equals(this.supportOdlUsers); + } + + + + public static Config getInstance() throws IOException { + if(_instance==null) { + _instance = load(DEFAULT_CONFIGFILENAME); + } + return _instance; + } + + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/CustomObjectMapper.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/CustomObjectMapper.java new file mode 100644 index 000000000..aa23d4dc1 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/CustomObjectMapper.java @@ -0,0 +1,39 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class CustomObjectMapper extends ObjectMapper{ + + private static final long serialVersionUID = 1L; + + + public CustomObjectMapper() { + this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.setSerializationInclusion(Include.NON_NULL); + this.enable(MapperFeature.USE_GETTERS_AS_SETTERS); + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakRole.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakRole.java new file mode 100644 index 000000000..67186baa7 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakRole.java @@ -0,0 +1,80 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +public class KeycloakRole { + private String id; + private String name; + private String description; + private boolean composite; + private boolean clientRole; + private String containerId; // realmname + + public String getName() { + return name; + } + + public boolean isClientRole() { + return clientRole; + } + + public void setClientRole(boolean clientRole) { + this.clientRole = clientRole; + } + + public String getContainerId() { + return containerId; + } + + public void setContainerId(String containerId) { + this.containerId = containerId; + } + + public boolean isComposite() { + return composite; + } + + public void setComposite(boolean composite) { + this.composite = composite; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setName(String name) { + this.name = name; + } + +}
\ No newline at end of file diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakUserTokenPayload.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakUserTokenPayload.java new file mode 100644 index 000000000..c99ec0d71 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/KeycloakUserTokenPayload.java @@ -0,0 +1,231 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * { + "exp": 1610362593, + "iat": 1610361393, + "jti": "09bd6f2c-5dba-44a0-bd76-cd0d440137d0", + "iss": "http://10.20.11.160:8080/auth/realms/onap", + "aud": "account", + "sub": "446a24bc-d8a0-43dd-afa5-e56eed75deb8", + "typ": "Bearer", + "azp": "admin-cli", + "session_state": "db2c96f4-cc9b-47e8-a83f-a01c50d656f2", + "acr": "1", + "realm_access": { + "roles": [ + "provision", + "offline_access", + "uma_authorization" + ] + }, + "resource_access": { + "account": { + "roles": [ + "manage-account", + "manage-account-links", + "view-profile" + ] + } + }, + "scope": "profile email", + "email_verified": false, + "name": "Luke Skywalker", + "preferred_username": "luke.skywalker", + "given_name": "Luke", + "family_name": "Skywalker", + "email": "luke.skywalker@sdnr.onap.org" +} + * @author jack + * + */ +public class KeycloakUserTokenPayload { + + private long exp; + private long iat; + private String jti; + private String iss; + private String aud; + private String sub; + private String typ; + private String azp; + @JsonProperty("session_state") + private String sessionState; + private String acr; + @JsonProperty("realm_access") + private RealmAccessData realmAccess; + @JsonProperty("resource_access") + private ResourceAccessData resourceAccess; + private String scope; + @JsonProperty("email_verified") + private String emailVerified; + private String name; + @JsonProperty("preferred_username") + private String preferredUsername; + @JsonProperty("given_name") + private String givenName; + @JsonProperty("family_name") + private String familyName; + private String email; + + public long getExp() { + return exp; + } + public void setExp(long exp) { + this.exp = exp; + } + public long getIat() { + return iat; + } + public void setIat(long iat) { + this.iat = iat; + } + public String getJti() { + return jti; + } + public void setJti(String jti) { + this.jti = jti; + } + public String getIss() { + return iss; + } + public void setIss(String iss) { + this.iss = iss; + } + public String getAud() { + return aud; + } + public void setAud(String aud) { + this.aud = aud; + } + public String getSub() { + return sub; + } + public void setSub(String sub) { + this.sub = sub; + } + public String getTyp() { + return typ; + } + public void setTyp(String typ) { + this.typ = typ; + } + public String getAzp() { + return azp; + } + public void setAzp(String azp) { + this.azp = azp; + } + public String getSessionState() { + return sessionState; + } + public void setSessionState(String sessionState) { + this.sessionState = sessionState; + } + public String getAcr() { + return acr; + } + public void setAcr(String acr) { + this.acr = acr; + } + public RealmAccessData getRealmAccess() { + return realmAccess; + } + public void setRealmAccess(RealmAccessData realmAccess) { + this.realmAccess = realmAccess; + } + public ResourceAccessData getResourceAccess() { + return resourceAccess; + } + public void setResourceAccess(ResourceAccessData resourceAccess) { + this.resourceAccess = resourceAccess; + } + public String getScope() { + return scope; + } + public void setScope(String scope) { + this.scope = scope; + } + public String getEmailVerified() { + return emailVerified; + } + public void setEmailVerified(String emailVerified) { + this.emailVerified = emailVerified; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getPreferredUsername() { + return preferredUsername; + } + public void setPreferredUsername(String preferredUsername) { + this.preferredUsername = preferredUsername; + } + public String getGivenName() { + return givenName; + } + public void setGivenName(String givenName) { + this.givenName = givenName; + } + public String getFamilyName() { + return familyName; + } + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + + + public static class RealmAccessData { + private List<String> roles; + + public List<String> getRoles(){ + return this.roles; + } + public void setRoles(List<String> roles) { + this.roles = roles; + } + } + public static class ResourceAccessData { + private RealmAccessData account; + + public RealmAccessData getAccount() { + return this.account; + } + public void setAccount(RealmAccessData account) { + this.account = account; + } + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/NoDefinitionFoundException.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/NoDefinitionFoundException.java new file mode 100644 index 000000000..d13be9602 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/NoDefinitionFoundException.java @@ -0,0 +1,33 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +public class NoDefinitionFoundException extends Exception { + + private static final long serialVersionUID = 1L; + + public NoDefinitionFoundException(String message) { + super(message); + } + + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthProviderConfig.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthProviderConfig.java new file mode 100644 index 000000000..e4d67432f --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthProviderConfig.java @@ -0,0 +1,135 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.OAuthProviderFactory.OAuthProvider; + +public class OAuthProviderConfig { + + private String host; + private String clientId; + private String secret; + private String id; + private String title; + private String scope; + private OAuthProvider type; + + public OAuthProvider getType() { + return type; + } + + public OAuthProviderConfig(String id, String host, String clientId, String secret, String scope, + String title) { + this.id = id; + this.host = host; + this.clientId = clientId; + this.secret = secret; + this.scope = scope; + this.title = title; + } + + @Override + public String toString() { + return "OAuthProviderConfig [host=" + host + ", clientId=" + clientId + ", secret=" + secret + ", id=" + id + + ", title=" + title + ", scope=" + scope + ", type=" + type + "]"; + } + + public void setType(OAuthProvider type) { + this.type = type; + } + + public OAuthProviderConfig() { + this(null, null, null, null, null, null); + } + + public void setHost(String host) { + this.host = host; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public void setId(String id) { + this.id = id; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getId() { + return this.id; + } + + public String getHost() { + return this.host; + } + + public String getClientId() { + return this.clientId; + } + + public String getSecret() { + return this.secret; + } + + public String getTitle() { + return this.title; + } + + public String getScope() { + return this.scope; + } + + @JsonIgnore + public void handleEnvironmentVars() { + if (Config.isEnvExpression(id)) { + this.id = Config.getProperty(id, null); + } + if (Config.isEnvExpression(host)) { + this.host = Config.getProperty(host, null); + } + if (Config.isEnvExpression(clientId)) { + this.clientId = Config.getProperty(clientId, null); + } + if (Config.isEnvExpression(secret)) { + this.secret = Config.getProperty(secret, null); + } + if (Config.isEnvExpression(scope)) { + this.scope = Config.getProperty(scope, null); + } + if (Config.isEnvExpression(title)) { + this.title = Config.getProperty(title, null); + } + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthResponseData.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthResponseData.java new file mode 100644 index 000000000..806a62062 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthResponseData.java @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +public class OAuthResponseData { + + private String access_token; + private double expires_in; + private double refresh_expires_in; + private String refresh_token; + private String token_type; + + public OAuthResponseData() { + } + + public OAuthResponseData(String token) { + this.access_token = token; + } + + public String getAccess_token() { + return access_token; + } + + public String getToken_type() { + return token_type; + } + + public void setToken_type(String token_type) { + this.token_type = token_type; + } + + public String getRefresh_token() { + return refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } + + public double getRefresh_expires_in() { + return refresh_expires_in; + } + + public void setRefresh_expires_in(double refresh_expires_in) { + this.refresh_expires_in = refresh_expires_in; + } + + public double getExpires_in() { + return expires_in; + } + + public void setExpires_in(double expires_in) { + this.expires_in = expires_in; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + @Override + public String toString() { + return "OAuthResponseData [access_token=" + access_token + ", expires_in=" + expires_in + + ", refresh_expires_in=" + refresh_expires_in + ", refresh_token=" + refresh_token + ", token_type=" + + token_type + "]"; + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthToken.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthToken.java new file mode 100644 index 000000000..1a695f2b0 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OAuthToken.java @@ -0,0 +1,52 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; + +public class OAuthToken { + private final String access_token; + private final String token_type; + private final long expires_at; + + public OAuthToken(BearerToken btoken) { + this.access_token = btoken.getToken(); + this.token_type = "Bearer"; + DecodedJWT token = JWT.decode(this.access_token); + this.expires_at = token.getExpiresAt().getTime() / 1000L; + } + + public String getAccess_token() { + return access_token; + } + + public String getToken_type() { + return token_type; + } + + public long getExpires_at() { + return expires_at; + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OdlPolicy.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OdlPolicy.java new file mode 100644 index 000000000..28ef3b3b0 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/OdlPolicy.java @@ -0,0 +1,130 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +public class OdlPolicy { + + private String path; + private PolicyMethods methods; + + + public OdlPolicy() { + + } + + public OdlPolicy(String path, PolicyMethods methods) { + this.path = path; + this.methods = methods; + } + + public PolicyMethods getMethods() { + return methods; + } + + public void setMethods(PolicyMethods methods) { + this.methods = methods; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public static OdlPolicy allowAll(String path) { + return new OdlPolicy(path, PolicyMethods.allowAll()); + } + + public static OdlPolicy denyAll(String path) { + return new OdlPolicy(path, PolicyMethods.denyAll()); + } + + public static class PolicyMethods { + private boolean get; + private boolean post; + private boolean put; + private boolean delete; + private boolean patch; + + public PolicyMethods() { + + } + + public PolicyMethods(boolean get, boolean post, boolean put, boolean del, boolean patch) { + this.get = get; + this.post = post; + this.put = put; + this.delete = del; + this.patch = patch; + } + + public boolean isGet() { + return get; + } + + public void setGet(boolean get) { + this.get = get; + } + + public boolean isPost() { + return post; + } + + public void setPost(boolean post) { + this.post = post; + } + + public boolean isPut() { + return put; + } + + public void setPut(boolean put) { + this.put = put; + } + + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + } + + public boolean isPatch() { + return patch; + } + + public void setPatch(boolean patch) { + this.patch = patch; + } + + public static PolicyMethods allowAll() { + return new PolicyMethods(true, true, true, true, true); + } + + public static PolicyMethods denyAll() { + return new PolicyMethods(false, false, false, false, false); + } + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/UserTokenPayload.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/UserTokenPayload.java new file mode 100644 index 000000000..229cdbf78 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/UserTokenPayload.java @@ -0,0 +1,82 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; + +import java.util.List; + +public class UserTokenPayload { + + private List<String> roles; + private String preferredUsername; + private String givenName; + private String familyName; + private long exp; + + public long getExp() { + return exp; + } + + public void setPreferredUsername(String preferredUsername) { + this.preferredUsername = preferredUsername; + } + + public void setGivenName(String givenName) { + this.givenName = givenName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public void setExp(long exp) { + this.exp = exp; + } + + public String getPreferredUsername() { + return preferredUsername; + } + + public String getGivenName() { + return givenName; + } + + public String getFamilyName() { + return familyName; + } + + public List<String> getRoles() { + return this.roles; + } + + public void setRoles(List<String> roles) { + this.roles = roles; + } + + public static UserTokenPayload create(String username, List<String> roles) { + UserTokenPayload data = new UserTokenPayload(); + data.setPreferredUsername(username); + data.setRoles(roles); + return data; + } + + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/AuthHttpServlet.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/AuthHttpServlet.java new file mode 100644 index 000000000..f5d344d41 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/AuthHttpServlet.java @@ -0,0 +1,452 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.http; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.codec.Base64; +import org.jolokia.osgi.security.Authenticator; +import org.onap.ccsdk.features.sdnr.wt.common.http.BaseHTTPClient; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.NoDefinitionFoundException; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthToken; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OdlPolicy; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.AuthService; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.AuthService.PublicOAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.OAuthProviderFactory; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator; +import org.opendaylight.aaa.api.IDMStoreException; +import org.opendaylight.aaa.api.IdMService; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; +import org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619.ShiroConfiguration; +import org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619.shiro.configuration.Main; +import org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619.shiro.configuration.Urls; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthHttpServlet extends HttpServlet { + + private static final Logger LOG = LoggerFactory.getLogger(AuthHttpServlet.class.getName()); + private static final long serialVersionUID = 1L; + private static final String BASEURI = "/oauth"; + private static final String LOGINURI = BASEURI + "/login"; + //private static final String LOGOUTURI = BASEURI + "/logout"; + private static final String PROVIDERSURI = BASEURI + "/providers"; + public static final String REDIRECTURI = BASEURI + "/redirect"; + private static final String POLICIESURI = BASEURI + "/policies"; + //private static final String PROVIDERID_REGEX = "^\\" + BASEURI + "\\/providers\\/([^\\/]+)$"; + private static final String REDIRECTID_REGEX = "^\\" + BASEURI + "\\/redirect\\/([^\\/]+)$"; + //private static final Pattern PROVIDERID_PATTERN = Pattern.compile(PROVIDERID_REGEX); + private static final Pattern REDIRECTID_PATTERN = Pattern.compile(REDIRECTID_REGEX); + + private static final String DEFAULT_DOMAIN = "sdn"; + private static final String HEAEDER_AUTHORIZATION = "Authorization"; + + private static final String CLASSNAME_ODLBASICAUTH = + "org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter"; + private static final String CLASSNAME_ODLBEARERANDBASICAUTH = + "org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter2"; + private static final String CLASSNAME_ODLMDSALAUTH = + "org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter"; + + private final ObjectMapper mapper; + /* state <=> AuthProviderService> */ + private final Map<String, AuthService> providerStore; + private Authenticator odlAuthenticator; + private IdMService odlIdentityService; + private final TokenCreator tokenCreator; + private final Config config; + private ShiroConfiguration shiroConfiguration; + + public AuthHttpServlet() throws IOException { + this.tokenCreator = TokenCreator.getInstance(); + this.config = Config.getInstance(); + this.mapper = new ObjectMapper(); + this.providerStore = new HashMap<>(); + for (OAuthProviderConfig pc : config.getProviders()) { + this.providerStore.put(pc.getId(), OAuthProviderFactory.create(pc.getType(), pc, + this.config.getRedirectUri(), TokenCreator.getInstance())); + } + + } + + public void setOdlAuthenticator(Authenticator odlAuthenticator) { + this.odlAuthenticator = odlAuthenticator; + } + + public void setOdlIdentityService(IdMService odlIdentityService) { + this.odlIdentityService = odlIdentityService; + } + + public void setShiroConfiguration(ShiroConfiguration shiroConfiguration) { + this.shiroConfiguration = shiroConfiguration; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + LOG.debug("GET request for {}", req.getRequestURI()); + fillHost(req); + if (PROVIDERSURI.equals(req.getRequestURI())) { + this.sendResponse(resp, HttpServletResponse.SC_OK, getConfigs(this.providerStore.values())); + } else if (POLICIESURI.equals(req.getRequestURI())) { + this.sendResponse(resp, HttpServletResponse.SC_OK, this.getPoliciesForUser(req)); + } else if (req.getRequestURI().startsWith(REDIRECTURI)) { + this.handleRedirect(req, resp); + } else { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + } + + } + + /** + * find out what urls can be accessed by user and which are forbidden + * + * urlEntries: "anon" -> any access allowed "authcXXX" -> no grouping rule -> any access for user allowed "authcXXX, + * roles[abc] -> user needs to have role abc "authcXXX, roles["abc,def"] -> user needs to have roles abc AND def + * "authcXXX, anyroles[abc] -> user needs to have role abc "authcXXX, anyroles["abc,def"] -> user needs to have + * roles abc OR def + * + * + * @param req + * @return + */ + private List<OdlPolicy> getPoliciesForUser(HttpServletRequest req) { + List<Urls> urlRules = this.shiroConfiguration.getUrls(); + UserTokenPayload data = this.getUserInfo(req); + List<OdlPolicy> policies = new ArrayList<>(); + if (urlRules != null) { + LOG.debug("try to find rules for user {} with roles {}", + data == null ? "null" : data.getPreferredUsername(), data == null ? "null" : data.getRoles()); + final String regex = "^([^,]+)[,]?[\\ ]?([anyroles]+)?(\\[\"?([a-zA-Z,]+)\"?\\])?"; + final Pattern pattern = Pattern.compile(regex); + Matcher matcher; + for (Urls urlRule : urlRules) { + matcher = pattern.matcher(urlRule.getPairValue()); + if (matcher.find()) { + try { + final String authClass = getAuthClass(matcher.group(1)); + Optional<OdlPolicy> policy = Optional.empty(); + if (authClass.equals(CLASSNAME_ODLBASICAUTH) + || authClass.equals(CLASSNAME_ODLBEARERANDBASICAUTH)) { + policy = this.getTokenBasedPolicy(urlRule, matcher, data); + } else if (authClass.equals(CLASSNAME_ODLMDSALAUTH)) { + policy = this.getMdSalBasedPolicy(urlRule, matcher, data); + } + if (policy.isPresent()) { + policies.add(policy.get()); + } else { + LOG.warn("unable to get policy for authClass {} for entry {}", authClass, + urlRule.getPairValue()); + } + } catch (NoDefinitionFoundException e) { + LOG.warn("unknown authClass: ", e); + } + + } else { + LOG.warn("unable to detect url role value: {}", urlRule.getPairValue()); + } + } + } else { + LOG.debug("no url rules found"); + } + return policies; + } + + /** + * extract policy rule for user from MD-SAL + * not yet supported + * @param urlRule + * @param matcher + * @param data + * @return + */ + private Optional<OdlPolicy> getMdSalBasedPolicy(Urls urlRule, Matcher matcher, UserTokenPayload data) { + + return Optional.empty(); + } + + /** + * extract policy rule for user from url rules of config + * @param urlRule + * @param matcher + * @param data + * @return + */ + private Optional<OdlPolicy> getTokenBasedPolicy(Urls urlRule, Matcher matcher, UserTokenPayload data) { + final String url = urlRule.getPairKey(); + if (!urlRule.getPairValue().contains(",")) { + LOG.debug("found rule without roles for '{}'", matcher.group(1)); + //not important if anon or authcXXX + if (data != null || "anon".equals(matcher.group(1))) { + return Optional.of(OdlPolicy.allowAll(url)); + } + } else if (data != null) { + LOG.debug("found rule with roles '{}'", matcher.group(4)); + if ("roles".equals(matcher.group(2))) { + if (this.rolesMatch(data.getRoles(), Arrays.asList(matcher.group(4).split(",")), false)) { + Optional.of(OdlPolicy.allowAll(url)); + } else { + Optional.of(OdlPolicy.denyAll(url)); + } + } else if ("anyroles".equals(matcher.group(2))) { + if (this.rolesMatch(data.getRoles(), Arrays.asList(matcher.group(4).split(",")), true)) { + Optional.of(OdlPolicy.allowAll(url)); + } else { + Optional.of(OdlPolicy.denyAll(url)); + } + } else { + LOG.warn("unable to detect url role value: {}", urlRule.getPairValue()); + } + } else { + return Optional.of(OdlPolicy.denyAll(url)); + } + return Optional.empty(); + } + + private String getAuthClass(String key) throws NoDefinitionFoundException { + if ("anon".equals(key)) { + return null; + } + Optional<Main> main = + this.shiroConfiguration.getMain().stream().filter((e) -> e.getPairKey().equals(key)).findFirst(); + if (main.isPresent()) { + return main.get().getPairValue(); + } + throw new NoDefinitionFoundException("unable to find def for " + key); + } + + private UserTokenPayload getUserInfo(HttpServletRequest req) { + if (isBearer(req)) { + UserTokenPayload data = TokenCreator.getInstance().decode(req); + if (data != null) { + return data; + } + } else if (isBasic(req)) { + String username = getBasicAuthUsername(req); + if (username != null) { + final String domain = getBasicAuthDomain(username); + if (!username.contains("@")) { + username = String.format("%s@%s", username, domain); + } + List<String> roles = this.odlIdentityService.listRoles(username, domain); + return UserTokenPayload.create(username, roles); + } + } + return null; + } + + private static String getBasicAuthDomain(String username) { + if (username.contains("@")) { + return username.split("@")[1]; + } + return DEFAULT_DOMAIN; + } + + private static String getBasicAuthUsername(HttpServletRequest req) { + final String header = req.getHeader(HEAEDER_AUTHORIZATION); + final String decoded = Base64.decodeToString(header.substring(6)); + // attempt to decode username/password; otherwise decode as token + if (decoded.contains(":")) { + return decoded.split(":")[0]; + } + LOG.warn("unable to detect username from basicauth header {}", header); + return null; + } + + private static boolean isBasic(HttpServletRequest req) { + final String header = req.getHeader(HEAEDER_AUTHORIZATION); + return header == null ? false : header.startsWith("Basic"); + } + + private static boolean isBearer(HttpServletRequest req) { + final String header = req.getHeader(HEAEDER_AUTHORIZATION); + return header == null ? false : header.startsWith("Bearer"); + } + + private boolean rolesMatch(List<String> userRoles, List<String> policyRoles, boolean any) { + if (any) { + for (String policyRole : policyRoles) { + if (userRoles.contains(policyRole)) { + return true; + } + } + return false; + } else { + for (String policyRole : policyRoles) { + if (!userRoles.contains(policyRole)) { + return false; + } + } + return true; + } + + } + + private void fillHost(HttpServletRequest req) { + String hostUrl = this.config.getHost(); + if (hostUrl == null) { + final String tmp = req.getRequestURL().toString(); + final String regex = "^(http[s]{0,1}:\\/\\/[^\\/]+)"; + final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); + final Matcher matcher = pattern.matcher(tmp); + if (matcher.find()) { + hostUrl = matcher.group(1); + this.config.setHost(hostUrl); + } + } + + } + + private List<PublicOAuthProviderConfig> getConfigs(Collection<AuthService> values) { + List<PublicOAuthProviderConfig> configs = new ArrayList<>(); + for (AuthService svc : values) { + configs.add(svc.getConfig(this.config.getHost())); + } + return configs; + } + + /** + * GET /oauth/redirect/{providerID} + * + * @param req + * @param resp + * @throws IOException + */ + private void handleRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException { + final String uri = req.getRequestURI(); + final Matcher matcher = REDIRECTID_PATTERN.matcher(uri); + if (matcher.find()) { + AuthService provider = this.providerStore.getOrDefault(matcher.group(1), null); + if (provider != null) { + provider.setLocalHostUrl(this.config.getHost()); + provider.handleRedirect(req, resp); + return; + } + } + resp.sendError(HttpServletResponse.SC_FORBIDDEN); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + LOG.debug("POST request for {}", req.getRequestURI()); + if (this.config.doSupportOdlUsers() && LOGINURI.equals(req.getRequestURI())) { + final String username = req.getParameter("username"); + final String domain = req.getParameter("domain"); + BearerToken token = + this.doLogin(username, req.getParameter("password"), domain != null ? domain : DEFAULT_DOMAIN); + if (token != null) { + sendResponse(resp, HttpServletResponse.SC_OK, new OAuthToken(token)); + LOG.debug("login for odluser {} succeeded", username); + return; + } else { + LOG.debug("login failed"); + } + + } + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // final String uri = req.getRequestURI(); + // final Matcher matcher = PROVIDERID_PATTERN.matcher(uri); + // if (matcher.find()) { + // final String id = matcher.group(1); + // final OAuthProviderConfig config = this.mapper.readValue(req.getInputStream(), OAuthProviderConfig.class); + // //this.providerStore.put(id, config); + // sendResponse(resp, HttpServletResponse.SC_OK, ""); + // return; + // } + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // final String uri = req.getRequestURI(); + // final Matcher matcher = PROVIDERID_PATTERN.matcher(uri); + // if (matcher.find()) { + // final String id = matcher.group(1); + // this.providerStore.remove(id); + // sendResponse(resp, HttpServletResponse.SC_OK, ""); + // return; + // } + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + } + + private BearerToken doLogin(String username, String password, String domain) { + if (!username.contains("@")) { + username = String.format("%s@%s", username, domain); + } + HttpServletRequest req = new HeadersOnlyHttpServletRequest( + Map.of("Authorization", BaseHTTPClient.getAuthorizationHeaderValue(username, password))); + if (this.odlAuthenticator.authenticate(req)) { + try { + LOG.info("userids={}", this.odlIdentityService.listUserIDs()); + LOG.info("domains={}", this.odlIdentityService.listDomains(username)); + } catch (IDMStoreException e) { + + } + List<String> roles = this.odlIdentityService.listRoles(username, domain); + UserTokenPayload data = new UserTokenPayload(); + data.setPreferredUsername(username); + data.setFamilyName(""); + data.setGivenName(username); + data.setExp(this.tokenCreator.getDefaultExp()); + data.setRoles(roles); + return this.tokenCreator.createNewJWT(data); + + } + return null; + } + + + + private void sendResponse(HttpServletResponse resp, int code, Object data) throws IOException { + byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0]; + // output + resp.setStatus(code); + resp.setContentLength(output.length); + resp.setContentType("application/json"); + ServletOutputStream os = null; + os = resp.getOutputStream(); + os.write(output); + + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/HeadersOnlyHttpServletRequest.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/HeadersOnlyHttpServletRequest.java new file mode 100644 index 000000000..31b6d696f --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/HeadersOnlyHttpServletRequest.java @@ -0,0 +1,469 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +public class HeadersOnlyHttpServletRequest implements HttpServletRequest{ + + private final Map<String,String> headers; + + public HeadersOnlyHttpServletRequest(Map<String,String> headers) { + this.headers = headers; + } + @Override + public Object getAttribute(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration<String> getAttributeNames() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getCharacterEncoding() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setCharacterEncoding(String env) throws UnsupportedEncodingException { + // TODO Auto-generated method stub + + } + + @Override + public int getContentLength() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getContentLengthLong() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getContentType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getParameter(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration<String> getParameterNames() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String[] getParameterValues(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map<String, String[]> getParameterMap() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getProtocol() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getScheme() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getServerName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getServerPort() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public BufferedReader getReader() throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRemoteAddr() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRemoteHost() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAttribute(String name, Object o) { + // TODO Auto-generated method stub + + } + + @Override + public void removeAttribute(String name) { + // TODO Auto-generated method stub + + } + + @Override + public Locale getLocale() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration<Locale> getLocales() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isSecure() { + // TODO Auto-generated method stub + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRealPath(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRemotePort() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getLocalName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getLocalAddr() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getLocalPort() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public ServletContext getServletContext() { + // TODO Auto-generated method stub + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + // TODO Auto-generated method stub + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) + throws IllegalStateException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isAsyncStarted() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isAsyncSupported() { + // TODO Auto-generated method stub + return false; + } + + @Override + public AsyncContext getAsyncContext() { + // TODO Auto-generated method stub + return null; + } + + @Override + public DispatcherType getDispatcherType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getAuthType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Cookie[] getCookies() { + // TODO Auto-generated method stub + return null; + } + + @Override + public long getDateHeader(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getHeader(String name) { + return this.headers.getOrDefault(name,null); + } + + @Override + public Enumeration<String> getHeaders(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration<String> getHeaderNames() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getIntHeader(String name) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getMethod() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getPathInfo() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getPathTranslated() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getContextPath() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getQueryString() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRemoteUser() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isUserInRole(String role) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Principal getUserPrincipal() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRequestedSessionId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRequestURI() { + // TODO Auto-generated method stub + return null; + } + + @Override + public StringBuffer getRequestURL() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getServletPath() { + // TODO Auto-generated method stub + return null; + } + + @Override + public HttpSession getSession(boolean create) { + // TODO Auto-generated method stub + return null; + } + + @Override + public HttpSession getSession() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String changeSessionId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void login(String username, String password) throws ServletException { + // TODO Auto-generated method stub + + } + + @Override + public void logout() throws ServletException { + // TODO Auto-generated method stub + + } + + @Override + public Collection<Part> getParts() throws IOException, ServletException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Part getPart(String name) throws IOException, ServletException { + // TODO Auto-generated method stub + return null; + } + + @Override + public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException { + // TODO Auto-generated method stub + return null; + } + + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappedBaseHttpResponse.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappedBaseHttpResponse.java new file mode 100644 index 000000000..6b1a8eddd --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappedBaseHttpResponse.java @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.onap.ccsdk.features.sdnr.wt.common.http.BaseHTTPResponse; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.CustomObjectMapper; + +public class MappedBaseHttpResponse<T> { + + + public static final int CODE404 = 404; + public static final int CODE200 = 200; + public static final MappedBaseHttpResponse<String> UNKNOWN = new MappedBaseHttpResponse<>(-1, null); + private static final ObjectMapper mapper = new CustomObjectMapper(); + public final int code; + public final T body; + + public MappedBaseHttpResponse(int code, String body, Class<T> clazz) + throws JsonMappingException, JsonProcessingException { + this(code, body != null ? mapper.readValue(body, clazz) : null); + } + + private MappedBaseHttpResponse(int code, T body) { + this.code = code; + this.body = body; + } + + public MappedBaseHttpResponse(BaseHTTPResponse response, Class<T> clazz) + throws JsonMappingException, JsonProcessingException { + this(response.code, response.body, clazz); + } + + @Override + public String toString() { + return "BaseHTTPResponse [code=" + code + ", body=" + body + "]"; + } + + public boolean isSuccess() { + return this.code == CODE200; + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappingBaseHttpClient.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappingBaseHttpClient.java new file mode 100644 index 000000000..ca455dc72 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/client/MappingBaseHttpClient.java @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import org.onap.ccsdk.features.sdnr.wt.common.http.BaseHTTPClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MappingBaseHttpClient extends BaseHTTPClient { + + private static Logger LOG = LoggerFactory.getLogger(MappingBaseHttpClient.class); + + public MappingBaseHttpClient(String base, boolean trustAllCerts) { + super(base, trustAllCerts); + } + + public MappingBaseHttpClient(String host) { + super(host); + } + + public <T> Optional<MappedBaseHttpResponse<String>> sendMappedRequest(String uri, String method, String body, + Map<String, String> headers) { + return this.sendMappedRequest(uri, method, body != null ? body.getBytes(CHARSET) : null, headers, String.class); + } + + public <T> Optional<MappedBaseHttpResponse<T>> sendMappedRequest(String uri, String method, String body, + Map<String, String> headers, Class<T> clazz) { + return this.sendMappedRequest(uri, method, body != null ? body.getBytes(CHARSET) : null, headers, clazz); + } + + protected <T> Optional<MappedBaseHttpResponse<T>> sendMappedRequest(String uri, String method, byte[] body, + Map<String, String> headers, Class<T> clazz) { + try { + return Optional.of(new MappedBaseHttpResponse<T>(this.sendRequest(uri, method, body, headers), clazz)); + } catch (IOException e) { + LOG.warn("problem during request for {}: ", uri, e); + } + return Optional.empty(); + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/AuthService.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/AuthService.java new file mode 100644 index 000000000..ec685a003 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/AuthService.java @@ -0,0 +1,257 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthResponseData; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.AuthHttpServlet; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappingBaseHttpClient; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AuthService { + + + private static final Logger LOG = LoggerFactory.getLogger(AuthService.class.getName()); + private final MappingBaseHttpClient httpClient; + protected final ObjectMapper mapper; + protected final OAuthProviderConfig config; + protected final TokenCreator tokenCreator; + private final String redirectUri; + private String localHostUrl; + public void setLocalHostUrl(String url) { + this.localHostUrl = url; + } + protected abstract String getTokenVerifierUri(); + + protected abstract Map<String, String> getAdditionalTokenVerifierParams(); + + protected abstract ResponseType getResponseType(); + + protected abstract boolean doSeperateRolesRequest(); + + protected abstract UserTokenPayload mapAccessToken(String spayload) + throws JsonMappingException, JsonProcessingException; + + protected abstract String getLoginUrl(String callbackUrl); + + public AuthService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) { + this.config = config; + this.tokenCreator = tokenCreator; + this.redirectUri = redirectUri; + this.mapper = new ObjectMapper(); + this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.httpClient = new MappingBaseHttpClient(this.config.getHost()); + } + + public PublicOAuthProviderConfig getConfig(String host) { + return new PublicOAuthProviderConfig(this, host); + } + + public void handleRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException { + switch (this.getResponseType()) { + case CODE: + this.handleRedirectCode(req, resp); + break; + case TOKEN: + sendErrorResponse(resp, "not yet implemented"); + break; + case SESSION_STATE: + break; + } + + } + + private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException { + resp.sendError(HttpServletResponse.SC_NOT_FOUND, message); + + } + + private void handleRedirectCode(HttpServletRequest req, HttpServletResponse resp) throws IOException { + final String code = req.getParameter("code"); + OAuthResponseData response = this.getTokenForUser(code); + if (response != null) { + if (this.doSeperateRolesRequest()) { + + } else { + this.handleUserInfoToken(response.getAccess_token(), resp); + } + } else { + sendErrorResponse(resp, "unable to verify code"); + } + + } + + private void handleUserInfoToken(String accessToken, HttpServletResponse resp) throws IOException { + try { + DecodedJWT jwt = JWT.decode(accessToken); + + String spayload = base64Decode(jwt.getPayload()); + LOG.debug("payload in jwt from keycload='{}'", spayload); + + UserTokenPayload data = this.mapAccessToken(spayload); + BearerToken onapToken = this.tokenCreator.createNewJWT(data); + sendTokenResponse(resp, onapToken); + } catch (JWTDecodeException | JsonProcessingException e) { + LOG.warn("unable to decode jwt token {}: ", accessToken, e); + sendErrorResponse(resp, e.getMessage()); + } + } + + + private void sendTokenResponse(HttpServletResponse resp, BearerToken data) throws IOException { + if (this.redirectUri == null) { + byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0]; + resp.setStatus(200); + resp.setContentLength(output.length); + resp.setContentType("application/json"); + ServletOutputStream os = null; + os = resp.getOutputStream(); + os.write(output); + } else { + resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + resp.setHeader("Location", assembleUrl(this.localHostUrl, this.redirectUri, data.getToken())); + } + } + + + + private static String base64Decode(String data) { + return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8); + } + + private OAuthResponseData getTokenForUser(String code) { + + Map<String, String> headers = new HashMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + Map<String, String> params = this.getAdditionalTokenVerifierParams(); + params.put("code", code); + params.put("client_id", this.config.getClientId()); + params.put("client_secret", this.config.getSecret()); + params.put("redirect_uri", + assembleRedirectUrl(localHostUrl, AuthHttpServlet.REDIRECTURI, this.config.getId())); + StringBuilder body = new StringBuilder(); + for (Entry<String, String> p : params.entrySet()) { + body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue()))); + } + + Optional<MappedBaseHttpResponse<OAuthResponseData>> response = this.httpClient.sendMappedRequest(this.getTokenVerifierUri(), + "POST", body.substring(0, body.length() - 1), headers, OAuthResponseData.class); + if (response.isPresent() && response.get().isSuccess()) { + return response.get().body; + } + LOG.warn("problem get token for code {}", code); + + return null; + } + + + + /** + * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g. + * http://10.20.0.11:8181/oauth/redirect/keycloak + * + * @param host + * @param baseUri + * @param serviceId + * @return + */ + public static String assembleRedirectUrl(String host, String baseUri, String serviceId) { + return String.format("%s%s/%s", host, baseUri, serviceId); + } + + private static String assembleUrl(String host, String uri, String token) { + return String.format("%s%s%s", host, uri, token); + } + + public static String urlEncode(String s) { + return URLEncoder.encode(s, StandardCharsets.UTF_8); + } + + public enum ResponseType { + CODE, TOKEN, SESSION_STATE + } + + + public static class PublicOAuthProviderConfig { + + private String id; + private String title; + private String loginUrl; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getLoginUrl() { + return loginUrl; + } + + public void setLoginUrl(String loginUrl) { + this.loginUrl = loginUrl; + } + + public PublicOAuthProviderConfig(AuthService authService, String host) { + this.id = authService.config.getId(); + this.title = authService.config.getTitle(); + this.loginUrl = authService.getLoginUrl( + assembleRedirectUrl(host, AuthHttpServlet.REDIRECTURI, this.id)); + } + + } + + + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/KeycloakProviderService.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/KeycloakProviderService.java new file mode 100644 index 000000000..3bfbb3b25 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/KeycloakProviderService.java @@ -0,0 +1,97 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.KeycloakUserTokenPayload; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload; + +public class KeycloakProviderService extends AuthService { + + public static final String ID = "keycloak"; + private Map<String, String> additionalTokenVerifierParams; + + public KeycloakProviderService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) { + super(config, redirectUri, tokenCreator); + this.additionalTokenVerifierParams = new HashMap<>(); + this.additionalTokenVerifierParams.put("grant_type", "authorization_code"); + } + + @Override + protected String getTokenVerifierUri() { + return "/auth/realms/onap/protocol/openid-connect/token"; + } + + @Override + protected String getLoginUrl(String callbackUrl) { + return String.format( + "%s/auth/realms/onap/protocol/openid-connect/auth?client_id=%s&response_type=code&scope=%s&redirect_uri=%s", + this.config.getHost(), urlEncode(this.config.getClientId()), this.config.getScope(), + urlEncode(callbackUrl)); + } + + + + private List<String> mapRoles(List<String> data) { + + List<String> filteredRoles = + data.stream().filter(role -> !role.equals("uma_authorization") && !role.equals("offline_access")) + .map(r -> r).collect(Collectors.toList()); + return filteredRoles; + } + + @Override + protected ResponseType getResponseType() { + return ResponseType.CODE; + } + + @Override + protected Map<String, String> getAdditionalTokenVerifierParams() { + return this.additionalTokenVerifierParams; + + } + + @Override + protected boolean doSeperateRolesRequest() { + return false; + } + + @Override + protected UserTokenPayload mapAccessToken(String spayload) throws JsonMappingException, JsonProcessingException { + KeycloakUserTokenPayload payload = mapper.readValue(spayload, KeycloakUserTokenPayload.class); + UserTokenPayload data = new UserTokenPayload(); + data.setExp(payload.getExp() * 1000L); + data.setFamilyName(payload.getFamilyName()); + data.setGivenName(payload.getGivenName()); + data.setPreferredUsername(payload.getPreferredUsername()); + data.setRoles(this.mapRoles(payload.getRealmAccess().getRoles())); + return data; + } + + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/NextcloudProviderService.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/NextcloudProviderService.java new file mode 100644 index 000000000..ad1da68b3 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/NextcloudProviderService.java @@ -0,0 +1,73 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import java.util.Map; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload; + +public class NextcloudProviderService extends AuthService { + + public NextcloudProviderService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) { + super(config, redirectUri, tokenCreator); + // TODO Auto-generated constructor stub + } + + @Override + protected String getTokenVerifierUri() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Map<String, String> getAdditionalTokenVerifierParams() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected ResponseType getResponseType() { + // TODO Auto-generated method stub + return ResponseType.TOKEN; + } + + @Override + protected boolean doSeperateRolesRequest() { + // TODO Auto-generated method stub + return false; + } + + @Override + protected UserTokenPayload mapAccessToken(String spayload) throws JsonMappingException, JsonProcessingException { + // TODO Auto-generated method stub + return null; + } + + @Override + protected String getLoginUrl(String callbackUrl) { + // TODO Auto-generated method stub + return null; + } + +}
\ No newline at end of file diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/OAuthProviderFactory.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/OAuthProviderFactory.java new file mode 100644 index 000000000..a09f150ef --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/OAuthProviderFactory.java @@ -0,0 +1,44 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers; + +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; + +public class OAuthProviderFactory { + + + public static AuthService create(OAuthProvider key, OAuthProviderConfig config, String redirectUri, + TokenCreator tokenCreator) { + switch (key) { + case KEYCLOAK: + return new KeycloakProviderService(config, redirectUri, tokenCreator); + case NEXTCLOUD: + return new NextcloudProviderService(config, redirectUri, tokenCreator); + } + return null; + } + + public static enum OAuthProvider { + KEYCLOAK, NEXTCLOUD + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/TokenCreator.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/TokenCreator.java new file mode 100644 index 000000000..3244f90e5 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/TokenCreator.java @@ -0,0 +1,106 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import java.util.Arrays; +import java.util.Date; +import javax.servlet.http.HttpServletRequest; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.AuthHttpServlet; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TokenCreator { + + private static final Logger LOG = LoggerFactory.getLogger(AuthHttpServlet.class.getName()); + private static final long DEFAULT_TOKEN_LIFETIME_MS = 30 * 60 * 1000; + private static final String TOKEN_ISSUER = Config.getProperty("${TOKEN_ISSUER}", "ONAP SDNC"); + private static TokenCreator _instance; + private static final String SECRET = Config.getProperty("${TOKEN_SECRET}", "secret"); + + private static final String ROLES_CLAIM = "roles"; + private static final String FAMILYNAME_CLAIM = "family_name"; + private static final String NAME_CLAIM = "name"; + + public static TokenCreator getInstance() { + if (_instance == null) { + _instance = new TokenCreator(); + } + return _instance; + } + + private TokenCreator() { + + } + + public BearerToken createNewJWT(UserTokenPayload data) { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + final String token = JWT.create().withIssuer(TOKEN_ISSUER).withExpiresAt(new Date(data.getExp())) + .withSubject(data.getPreferredUsername()).withClaim(NAME_CLAIM, data.getGivenName()) + .withClaim(FAMILYNAME_CLAIM, data.getFamilyName()) + .withArrayClaim(ROLES_CLAIM, data.getRoles().toArray(new String[data.getRoles().size()])) + .sign(algorithm); + return new BearerToken(token); + } + + public DecodedJWT verify(String token) { + DecodedJWT jwt = null; + LOG.debug("try to verify token {}", token); + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + JWTVerifier verifier = JWT.require(algorithm).withIssuer(TOKEN_ISSUER).build(); + jwt = verifier.verify(token); + + } catch (JWTVerificationException e) { + LOG.warn("unable to verify token {}:", token, e); + } + return jwt; + } + + public long getDefaultExp() { + return new Date().getTime() + DEFAULT_TOKEN_LIFETIME_MS; + } + + public UserTokenPayload decode(HttpServletRequest req) throws JWTDecodeException { + final String authHeader = req.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer")) { + return null; + } + DecodedJWT jwt = JWT.decode(authHeader.substring(7)); + UserTokenPayload data = new UserTokenPayload(); + data.setRoles(Arrays.asList(jwt.getClaim(ROLES_CLAIM).asArray(String.class))); + data.setExp(jwt.getExpiresAt().getTime()); + data.setFamilyName(jwt.getClaim(FAMILYNAME_CLAIM).asString()); + data.setGivenName(jwt.getClaim(NAME_CLAIM).asString()); + data.setPreferredUsername(jwt.getClaim(NAME_CLAIM).asString()); + + return data; + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestConfig.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestConfig.java new file mode 100644 index 000000000..2b88141ae --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestConfig.java @@ -0,0 +1,36 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.test; + +import java.io.IOException; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; + +public class TestConfig { + + @Test + public void test() throws IOException { + + Config config = Config.load("src/test/resources/test.config.json"); + System.out.println("config="+config); + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestDeserializer.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestDeserializer.java new file mode 100644 index 000000000..236c0ffac --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestDeserializer.java @@ -0,0 +1,101 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.KeycloakUserTokenPayload; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthResponseData; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse; + +public class TestDeserializer { + + @Test + public void test1() throws IOException { + final String token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1OHNXaTF4QWxjT1pyelY4X0l2VjliMlJTaFdZUWV4aXZYUXNYLTFTME" + + "RNIn0.eyJleHAiOjE2MTAzNjE2OTQsImlhdCI6MTYxMDM2MTM5NCwianRpIjoiOWRhOThmMTYtOTEyOS00N2NmLTgzOGQtNWQzYmVkYzYyZTJjIiwiaXNzIjoiaHR0cDovLzEwLjIwLjExLjE2MDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjE4MzhjNGYyLTVmZTMtNGYwYy1iMmQyLWQzNjRiMjdhNDk5NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJjYzcxZmMxZi1hZGQ0LTRhODYtYWU1ZS1jMzRkZjQwM2M3NzIiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.PUT4NzCM1ej3sNMMCkQa1NuQQwDgn19G-OnWL4NgLvZ3ocJUZ1Yfr9KAPkrJHaiK_HXQqwTA-Ma6Qn7BBMoXNdFjwu0k_HpqyUbBDilGN4wpkGiUeS1p5SW4T_hnWJtwCJ5BYkEvF6WaEbi7MFCbEVO9LVcUvsa-7St1WZ8V8RVfbWgjAu7ejlxe6RYUDMYzIKDj5F5y1-qCyoKzGIjt5ajcA9FWrexHifLJECKO8ZG08Wp7xQld1sYPOdde6XHMwiyNelTwd_EzCBgUw_8664rETGDVtyfuYchowo5Z6fmn4U87L6EGjEuxiAE8f3USy_jh6UF0LnvyTyq_9I" + + "M1VA"; + final String token2 = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1OHNXaTF4QWxjT1pyelY4X0l2VjliMlJTaFdZUWV4aXZYUXNYLTFTMERNIn0." + + "eyJleHAiOjE2MTAzNzA3MDcsImlhdCI6MTYxMDM3MDQwNywianRpIjoiMTczMmI0YzQtNDJlYS00ZWM4LTlhNjMtMTY2YTg4ZTk5ZjQ0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjE4MzhjNGYyLTVmZTMtNGYwYy1iMmQyLWQzNjRiMjdhNDk5NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJhZjVkYTk2NS1jYmIzLTQzOTYtYmNjNi1kZTBkMDUyOWMyNDgiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.G_1ByqQlPuJ6_5nuIECfY1VqGufzWQpnFKuOy8YPOOug_jJsIwhVo-JQJiKAxYbHbmDNLrpRJTFlSub0K-1AFyxMw0k_W_YLV0dOTqIakVMTKk9obHFAYtthvhdbt5zb9-33OdCRMMKjA-arj8UeOLEAeFkaeYYBARCD4mEnMFG0vzEiovCCD-jXsfISiS-lOYnCd3hWK8e0brk_bvauxS9W4Z6nptE2564wshe9N_j9-3bQRRAHiAt6f755PhbYgJAu87GdA0bLh_TDe6fie-03goIFMssHoq4n67i-8501UoIG_LccijnfexCS-YwxkfTLbz5d8PvsNadqvFlvig"; + final String response = + "{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1OHNXaTF4QWxjT1pyelY4X0l2VjliMlJTaFdZUWV4aXZYUXNYLTFTME" + + "RNIn0.eyJleHAiOjE2MTAzNjE2OTQsImlhdCI6MTYxMDM2MTM5NCwianRpIjoiOWRhOThmMTYtOTEyOS00N2NmLTgzOGQtNWQzYmVkYzYyZTJjIiwiaXNzIjoiaHR0cDovLzEwLjIwLjExLjE2MDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjE4MzhjNGYyLTVmZTMtNGYwYy1iMmQyLWQzNjRiMjdhNDk5NyIsInR5cCI6IkJlYXJlciIsImF6cCI6I" + + "mFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJjYzcxZmMxZi1hZGQ0LTRhODYtYWU1ZS1jMzRkZjQwM2M3NzIiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.PUT4NzCM1ej3sNMMCkQa1NuQQwDgn19G-OnWL4NgLvZ3ocJUZ1Yfr9KAPkrJHaiK_HX" + + "QqwTA-Ma6Qn7BBMoXNdFjwu0k_HpqyUbBDilGN4wpkGiUeS1p5SW4T_hnWJtwCJ5BYkEvF6WaEbi7MFCbEVO9LVcUvsa-7St1WZ8V8RVfbWgjAu7ejlxe6RYUDMYzIKDj5F5y1-qCyoKzGIjt5ajcA9FWrexHifLJECKO8ZG08Wp7xQld1sYPOdde6XHMwiyNelTwd_EzCBgUw_8664rETGDVtyfuYchowo5Z6fmn4U87L6EGjEuxiAE8f3USy_jh6UF0LnvyTyq_9I" + + "M1VA\",\"expires_in\":300,\"refresh_expires_in\":1800,\"refresh_token\":\"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1NzdiN2Q3MC00NzMwLTQ0MDMtODk4My04ZjJmYTg4M2U2M2EifQ.eyJleHAiOjE2MTAzNjMxOTQsImlhdCI6MTYxMDM2MTM5NCwianRpIjoiMmNjMGY4YWYtNWY2OC00YmFhLWEyOTctNjMxMjk2YzhmY2" + + "U5IiwiaXNzIjoiaHR0cDovLzEwLjIwLjExLjE2MDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6Imh0dHA6Ly8xMC4yMC4xMS4xNjA6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiIxODM4YzRmMi01ZmUzLTRmMGMtYjJkMi1kMzY0YjI3YTQ5OTciLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYWRtaW4tY2xpIiwic2Vzc2lvbl9zdGF0ZSI6I" + + "mNjNzFmYzFmLWFkZDQtNGE4Ni1hZTVlLWMzNGRmNDAzYzc3MiIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSJ9.qutqcFuQW-GzaCVNMfiYrbmHYD34GYwBqIbaQbJSY-g\",\"token_type\":\"bearer\",\"not-before-policy\":0,\"session_state\":\"cc71fc1f-add4-4a86-ae5e-c34df403c772\",\"scope\":\"email profile\"} "; + + OAuthResponseData data = new MappedBaseHttpResponse<>(200,response,OAuthResponseData.class).body; + assertEquals(token,data.getAccess_token()); + + } + + @Test + public void testUserPayloadDeser() throws JsonMappingException, JsonProcessingException { + final String payload = "{\n" + + " \"exp\": 1610362593,\n" + + " \"iat\": 1610361393,\n" + + " \"jti\": \"09bd6f2c-5dba-44a0-bd76-cd0d440137d0\",\n" + + " \"iss\": \"http://10.20.11.160:8080/auth/realms/onap\",\n" + + " \"aud\": \"account\",\n" + + " \"sub\": \"446a24bc-d8a0-43dd-afa5-e56eed75deb8\",\n" + + " \"typ\": \"Bearer\",\n" + + " \"azp\": \"admin-cli\",\n" + + " \"session_state\": \"db2c96f4-cc9b-47e8-a83f-a01c50d656f2\",\n" + + " \"acr\": \"1\",\n" + + " \"realm_access\": {\n" + + " \"roles\": [\n" + + " \"provision\",\n" + + " \"offline_access\",\n" + + " \"uma_authorization\"\n" + + " ]\n" + + " },\n" + + " \"resource_access\": {\n" + + " \"account\": {\n" + + " \"roles\": [\n" + + " \"manage-account\",\n" + + " \"manage-account-links\",\n" + + " \"view-profile\"\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"scope\": \"profile email\",\n" + + " \"email_verified\": false,\n" + + " \"name\": \"Luke Skywalker\",\n" + + " \"preferred_username\": \"luke.skywalker\",\n" + + " \"given_name\": \"Luke\",\n" + + " \"family_name\": \"Skywalker\",\n" + + " \"email\": \"luke.skywalker@sdnr.onap.org\"\n" + + "}"; + + ObjectMapper mapper = new ObjectMapper(); + KeycloakUserTokenPayload data = mapper.readValue(payload,KeycloakUserTokenPayload.class); + assertNotNull(data.getRealmAccess()); + assertEquals(3, data.getRealmAccess().getRoles().size()); + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestProperty.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestProperty.java new file mode 100644 index 000000000..2d03bab1d --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestProperty.java @@ -0,0 +1,42 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : ccsdk features + * ================================================================================ + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ +package org.onap.ccsdk.features.sdnr.wt.oauthprovider.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import org.junit.Ignore; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; + +public class TestProperty { + + @Ignore + @Test + public void test() { + String hostname = Config.getProperty("${HOSTNAME}", null); + System.out.println(hostname); + assertNotNull(hostname); + hostname = Config.getProperty("${HOSTNAME2}", null); + System.out.println(hostname); + assertNull(hostname); + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/test.config.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/test.config.json new file mode 100644 index 000000000..88137fdad --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/test.config.json @@ -0,0 +1,18 @@ +{ + "tokenSecret": "${OAUTH_TOKEN_SECRET}", + "tokenIssuer": "${OAUTH_TOKEN_ISSUER}", + "host": "", + "redirectUri": "${OAUTH_ODLUX_REDIRECT_URI}", + "supportOdlUsers": "${OAUTH_SUPPORT_ODLUSERS}", + "providers": [ + { + "id": "keycloak", + "type": "KEYCLOAK", + "host": "http://10.20.11.160:8080", + "clientId": "odlux.app", + "secret": "5da4ea3d-8cc9-4669-bd7e-3ecb91d120cd", + "scope": "openid", + "title": "OSNL Keycloak Provider" + } + ] +}
\ No newline at end of file |