diff options
Diffstat (limited to 'sdnr/wt/oauth-provider/provider-jar/src/main')
15 files changed, 407 insertions, 60 deletions
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 index 6dbed1f85..908b91dcf 100644 --- 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 @@ -26,15 +26,16 @@ 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.BearerToken; 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.data.InvalidConfigurationException; import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator; import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal; -import org.apache.shiro.authc.BearerToken; import org.opendaylight.aaa.shiro.realm.TokenAuthRealm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +47,7 @@ public class OAuth2Realm extends TokenAuthRealm { private final TokenCreator tokenCreator; private final Config config; - public OAuth2Realm() throws IOException { + public OAuth2Realm() throws IllegalArgumentException, IOException, InvalidConfigurationException { super(); super.setName(REALM_NAME); this.config = Config.getInstance(); 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 index 3ebc144d3..6798026f3 100644 --- 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 @@ -21,13 +21,16 @@ */ package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; +import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; +import java.security.SecureRandom; +import java.util.Arrays; import java.util.List; -import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; @@ -44,25 +47,40 @@ public class Config { 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 Random random; + public static final String TOKENALG_HS256 = "HS256"; + public static final String TOKENALG_RS256 = "RS256"; + public static final String TOKENALG_RS512 = "RS512"; + private static final String CLIENTALG_PRE = "Client"; + public static final String TOKENALG_CLIENT_RS256 = CLIENTALG_PRE + TOKENALG_RS256; + public static final String TOKENALG_CLIENT_RS512 = CLIENTALG_PRE + TOKENALG_RS512; + private static final String DEFAULT_TOKEN_ALGORITHM = TOKENALG_HS256; + + private static final long DEFAULT_TOKEN_LIFETIME = 30 * 60; + private static final List<String> VALID_ALGORITHMS = + Arrays.asList(TOKENALG_HS256, TOKENALG_RS256, TOKENALG_RS512, TOKENALG_CLIENT_RS256, TOKENALG_CLIENT_RS512); + private static final List<String> VALID_ALGORITHMS_FOR_INTERNAL_LOGIN = + Arrays.asList(TOKENALG_HS256, TOKENALG_RS256, TOKENALG_RS512); + private static SecureRandom random; private static Config _instance; private List<OAuthProviderConfig> providers; private String redirectUri; private String supportOdlUsers; private String tokenSecret; + private String tokenPubKey; + private String algorithm; private String tokenIssuer; private String publicUrl; - + private long tokenLifetime; @Override public String toString() { return "Config [providers=" + providers + ", redirectUri=" + redirectUri + ", supportOdlUsers=" - + supportOdlUsers + ", tokenSecret=" + tokenSecret + ", tokenIssuer=" + tokenIssuer + "]"; + + supportOdlUsers + ", tokenSecret=***, tokenPubKey=" + tokenPubKey + ", algorithm=" + algorithm + + ", tokenIssuer=" + tokenIssuer + ", publicUrl=" + publicUrl + ", tokenLifetime=" + tokenLifetime + + "]"; } - - public List<OAuthProviderConfig> getProviders() { return providers; } @@ -95,6 +113,24 @@ public class Config { this.tokenSecret = tokenSecret; } + public String getAlgorithm() { + return this.algorithm; + } + + public void setAlgorithm(String alg) { + this.algorithm = alg; + } + + @JsonGetter("tokenPubKey") + public String getPublicKey() { + return this.tokenPubKey; + } + + @JsonSetter("tokenPubKey") + public void setPublicKey(String pubKey) { + this.tokenPubKey = pubKey; + } + public String getTokenIssuer() { return tokenIssuer; } @@ -103,7 +139,6 @@ public class Config { this.tokenIssuer = tokenIssuer; } - public String getPublicUrl() { return publicUrl; } @@ -112,25 +147,39 @@ public class Config { this.publicUrl = publicUrl; } + public long getTokenLifetime() { + return this.tokenLifetime; + } + + public void setTokenLifetime(long lifetime) { + this.tokenLifetime = lifetime; + } + @JsonIgnore private void handleEnvironmentVars() { - if (isEnvExpression(tokenIssuer)) { - this.tokenIssuer = getProperty(tokenIssuer, null); + if (isEnvExpression(this.tokenIssuer)) { + this.tokenIssuer = getProperty(this.tokenIssuer, null); + } + if (isEnvExpression(this.tokenSecret)) { + this.tokenSecret = getProperty(this.tokenSecret, null); } - if (isEnvExpression(tokenSecret)) { - this.tokenSecret = getProperty(tokenSecret, null); + if (isEnvExpression(this.tokenPubKey)) { + this.tokenPubKey = getProperty(this.tokenPubKey, null); } - if (isEnvExpression(publicUrl)) { - this.publicUrl = getProperty(publicUrl, null); + if (isEnvExpression(this.algorithm)) { + this.algorithm = getProperty(this.algorithm, null); } - if (isEnvExpression(redirectUri)) { - this.redirectUri = getProperty(redirectUri, null); + if (isEnvExpression(this.publicUrl)) { + this.publicUrl = getProperty(this.publicUrl, null); } - if (isEnvExpression(supportOdlUsers)) { - this.supportOdlUsers = getProperty(supportOdlUsers, null); + if (isEnvExpression(this.redirectUri)) { + this.redirectUri = getProperty(this.redirectUri, null); + } + if (isEnvExpression(this.supportOdlUsers)) { + this.supportOdlUsers = getProperty(this.supportOdlUsers, null); } if (this.providers != null && !this.providers.isEmpty()) { - for(OAuthProviderConfig cfg : this.providers) { + for (OAuthProviderConfig cfg : this.providers) { cfg.handleEnvironmentVars(); } } @@ -138,21 +187,27 @@ public class Config { @JsonIgnore private void handleDefaultValues() { - if (tokenIssuer == null || tokenIssuer.isEmpty()) { + if (this.tokenIssuer == null || this.tokenIssuer.isEmpty()) { this.tokenIssuer = DEFAULT_TOKENISSUER; } - if (tokenSecret == null || tokenSecret.isEmpty()) { + if (this.algorithm == null || this.algorithm.isEmpty()) { + this.algorithm = DEFAULT_TOKEN_ALGORITHM; + } + if (TOKENALG_HS256.equals(this.algorithm) && (this.tokenSecret == null || this.tokenSecret.isEmpty())) { this.tokenSecret = DEFAULT_TOKENSECRET; } - if (redirectUri == null || redirectUri.isEmpty() || "null".equals(redirectUri)) { + if (this.redirectUri == null || this.redirectUri.isEmpty() || "null".equals(this.redirectUri)) { this.redirectUri = DEFAULT_REDIRECTURI; } - if (publicUrl != null && (publicUrl.isEmpty() || "null".equals(publicUrl))) { + if (this.publicUrl != null && (this.publicUrl.isEmpty() || "null".equals(this.publicUrl))) { this.publicUrl = null; } - if (supportOdlUsers == null || supportOdlUsers.isEmpty()) { + if (this.supportOdlUsers == null || this.supportOdlUsers.isEmpty()) { this.supportOdlUsers = DEFAULT_SUPPORTODLUSERS; } + if (this.tokenLifetime <= 0) { + this.tokenLifetime = DEFAULT_TOKEN_LIFETIME; + } } static boolean isEnvExpression(String key) { @@ -166,8 +221,8 @@ public class Config { public static String generateSecret(int targetStringLength) { int leftLimit = 48; // numeral '0' int rightLimit = 122; // letter 'z' - if(random==null) { - random = new Random(); + if (random == null) { + random = new SecureRandom(); } String generatedString = random.ints(leftLimit, rightLimit + 1) .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)).limit(targetStringLength) @@ -198,7 +253,7 @@ public class Config { String envvar = mkey.substring(2, mkey.length() - 1); String env = System.getenv(envvar); tmp = tmp.replace(mkey, env == null ? "" : env); - if (env != null && env.isEmpty()) { + if (env != null && !env.isEmpty()) { found = true; } } catch (SecurityException e) { @@ -218,7 +273,7 @@ public class Config { return value.equals("true"); } - public static Config load(String filename) throws IOException { + public static Config load(String filename) throws IOException, InvalidConfigurationException { CustomObjectMapper mapper = new CustomObjectMapper(); File file = new File(filename); if (!file.exists()) { @@ -228,26 +283,65 @@ public class Config { Config cfg = mapper.readValue(content, Config.class); cfg.handleEnvironmentVars(); cfg.handleDefaultValues(); + cfg.validate(); return cfg; } @JsonIgnore + private void validate() throws InvalidConfigurationException { + //verify that algorithm is supported + if (!VALID_ALGORITHMS.contains(this.algorithm)) { + throw new InvalidConfigurationException(String.format("Algorithm '%s' is not supported ", this.algorithm)); + } + //verify that set values are matching the algorithm + //if hs256 check if secret is set + if (this.algorithm.startsWith("HS")) { + if (this.tokenSecret == null || this.tokenSecret.isBlank()) { + throw new InvalidConfigurationException( + String.format("There is no secret set for algorithm '%s'", this.algorithm)); + } + } + //if rs256 or rs512 check if secret(private key) and pubkey are set + if (this.algorithm.startsWith("RS")) { + if (this.tokenSecret == null || this.tokenSecret.isBlank()) { + throw new InvalidConfigurationException( + String.format("There is no secret set for algorithm '%s'", this.algorithm)); + } + if (this.tokenPubKey == null || this.tokenPubKey.isBlank()) { + throw new InvalidConfigurationException( + String.format("There is no public key for algorithm '%s'", this.algorithm)); + } + } + //if client rs256 or client rs512 check if pubkey are set + if (this.algorithm.startsWith("Client")) { + if (this.tokenPubKey == null || this.tokenPubKey.isBlank()) { + throw new InvalidConfigurationException( + String.format("There is no public key for algorithm '%s'", this.algorithm)); + } + } + } + + @JsonIgnore public boolean doSupportOdlUsers() { return "true".equals(this.supportOdlUsers); } - public static Config getInstance() throws IOException { + public static Config getInstance() throws IOException, InvalidConfigurationException { return getInstance(DEFAULT_CONFIGFILENAME); } - public static Config getInstance(String filename) throws IOException { + public static Config getInstance(String filename) throws IOException, InvalidConfigurationException { if (_instance == null) { _instance = load(filename); } return _instance; } + public boolean loginActive() { + return VALID_ALGORITHMS_FOR_INTERNAL_LOGIN.contains(this.algorithm); + } + } diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/InvalidConfigurationException.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/InvalidConfigurationException.java new file mode 100644 index 000000000..a0e97de74 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/data/InvalidConfigurationException.java @@ -0,0 +1,32 @@ +/* + * ============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 InvalidConfigurationException extends Exception { + + public InvalidConfigurationException(String str) { + super(str); + } + + private static final long serialVersionUID = 1L; + +} 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 index b05d3948a..825286dd0 100644 --- 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 @@ -25,16 +25,19 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import org.apache.shiro.authc.BearerToken; + public class OAuthToken { private final String access_token; private final String token_type; private final long expires_at; + private final long issued_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; + this.issued_at = token.getIssuedAt().getTime() / 1000L; } public String getAccess_token() { @@ -48,5 +51,8 @@ public class OAuthToken { public long getExpires_at() { return expires_at; } + public long getIssued_at() { + return issued_at; + } } 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 index 229cdbf78..a983dd69f 100644 --- 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 @@ -30,11 +30,17 @@ public class UserTokenPayload { private String givenName; private String familyName; private long exp; + private long iat; + public long getExp() { return exp; } + public long getIat() { + return this.iat; + } + public void setPreferredUsername(String preferredUsername) { this.preferredUsername = preferredUsername; } @@ -51,6 +57,10 @@ public class UserTokenPayload { this.exp = exp; } + public void setIat(long iat) { + this.iat = iat; + } + public String getPreferredUsername() { return preferredUsername; } diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/AnyRoleHttpAuthenticationFilter.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/AnyRoleHttpAuthenticationFilter.java index 0dc58efff..e0714faf8 100644 --- a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/AnyRoleHttpAuthenticationFilter.java +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/filters/AnyRoleHttpAuthenticationFilter.java @@ -72,4 +72,4 @@ public class AnyRoleHttpAuthenticationFilter extends RolesAuthorizationFilter { LOG.debug("no role matched: access denied"); return false; } -}
\ 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/http/AuthHttpServlet.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/http/AuthHttpServlet.java index 686684f35..96faccba0 100644 --- 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 @@ -39,12 +39,14 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.SecurityUtils; import org.apache.shiro.ShiroException; +import org.apache.shiro.authc.BearerToken; import org.apache.shiro.codec.Base64; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; 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.InvalidConfigurationException; 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; @@ -56,7 +58,6 @@ import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.MdSalAuthorizatio 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.IdMService; -import org.apache.shiro.authc.BearerToken; import org.opendaylight.mdsal.binding.api.DataBroker; 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; @@ -101,7 +102,7 @@ public class AuthHttpServlet extends HttpServlet { private static ShiroConfiguration shiroConfiguration; private static MdSalAuthorizationStore mdsalAuthStore; - public AuthHttpServlet() throws IOException { + public AuthHttpServlet() throws IllegalArgumentException, IOException, InvalidConfigurationException { this.config = Config.getInstance(); this.tokenCreator = TokenCreator.getInstance(this.config); this.mapper = new ObjectMapper(); @@ -300,7 +301,7 @@ public class AuthHttpServlet extends HttpServlet { private UserTokenPayload getUserInfo(HttpServletRequest req) { if (isBearer(req)) { - UserTokenPayload data = TokenCreator.getInstance(this.config).decode(req); + UserTokenPayload data = this.tokenCreator.decode(req); if (data != null) { return data; } @@ -414,7 +415,7 @@ public class AuthHttpServlet extends HttpServlet { 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())) { + if (this.config.loginActive() && this.config.doSupportOdlUsers() && LOGINURI.equals(req.getRequestURI())) { final String username = req.getParameter("username"); final String domain = req.getParameter("domain"); BearerToken token = @@ -443,6 +444,7 @@ public class AuthHttpServlet extends HttpServlet { data.setPreferredUsername(username); data.setFamilyName(""); data.setGivenName(username); + data.setIat(this.tokenCreator.getDefaultIat()); data.setExp(this.tokenCreator.getDefaultExp()); data.setRoles(roles); return this.tokenCreator.createNewJWT(data); 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 index f16975f6f..bb0857ab6 100644 --- 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 @@ -41,13 +41,13 @@ import java.util.stream.Collectors; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.authc.BearerToken; 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.apache.shiro.authc.BearerToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,7 +74,7 @@ public abstract class AuthService { protected abstract String getLoginUrl(String callbackUrl); - protected abstract UserTokenPayload requestUserRoles(String access_token, long expires_at); + protected abstract UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at); protected abstract boolean verifyState(String state); @@ -128,7 +128,8 @@ public abstract class AuthService { if (this.doSeperateRolesRequest()) { //long expiresAt = this.tokenCreator.getDefaultExp(Math.round(response.getExpires_in())); long expiresAt = this.tokenCreator.getDefaultExp(); - UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), expiresAt); + long issuedAt = this.tokenCreator.getDefaultIat(); + UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), issuedAt, expiresAt); if (data != null) { this.handleUserInfoToken(data, resp, host); } else { diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/GitlabProviderService.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/GitlabProviderService.java index 4a8bdfa1b..1111603c9 100644 --- a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/GitlabProviderService.java +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/GitlabProviderService.java @@ -95,7 +95,7 @@ public class GitlabProviderService extends AuthService { } @Override - protected UserTokenPayload requestUserRoles(String access_token, long expires_at) { + protected UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at) { LOG.info("reqesting user roles with token={}", access_token); Map<String, String> authHeaders = new HashMap<>(); authHeaders.put("Authorization", String.format("Bearer %s", access_token)); @@ -116,6 +116,7 @@ public class GitlabProviderService extends AuthService { data.setPreferredUsername(uInfo.getUsername()); data.setGivenName(uInfo.getName()); data.setFamilyName(uInfo.getName()); + data.setIat(issued_at); data.setExp(expires_at); List<String> roles = new ArrayList<>(); GitlabGroupInfo[] uRoles = groupInfos.get().body; 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 index c226a14dc..dbc577664 100644 --- 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 @@ -84,6 +84,7 @@ public class KeycloakProviderService extends AuthService { protected UserTokenPayload mapAccessToken(String spayload) throws JsonMappingException, JsonProcessingException { KeycloakUserTokenPayload payload = mapper.readValue(spayload, KeycloakUserTokenPayload.class); UserTokenPayload data = new UserTokenPayload(); + data.setIat(payload.getIat() * 1000L); data.setExp(payload.getExp() * 1000L); data.setFamilyName(payload.getFamilyName()); data.setGivenName(payload.getGivenName()); @@ -93,7 +94,7 @@ public class KeycloakProviderService extends AuthService { } @Override - protected UserTokenPayload requestUserRoles(String access_token, long expires_at) { + protected UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at) { return null; } diff --git a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/MdSalAuthorizationStore.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/MdSalAuthorizationStore.java index 293fe33f9..ca7f47138 100644 --- a/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/MdSalAuthorizationStore.java +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/MdSalAuthorizationStore.java @@ -50,7 +50,9 @@ public class MdSalAuthorizationStore { public Optional<OdlPolicy> getPolicy(String path, List<String> userRoles) { InstanceIdentifier<Policies> iif = InstanceIdentifier.create(HttpAuthorization.class).child(Policies.class); Optional<Policies> odata = Optional.empty(); - try (ReadTransaction transaction = this.dataBroker.newReadOnlyTransaction()) { + // The implicite close is not handled correctly by underlaying opendaylight netconf service + ReadTransaction transaction = this.dataBroker.newReadOnlyTransaction(); + try { odata = transaction.read(LogicalDatastoreType.CONFIGURATION, iif).get(); } catch (ExecutionException e) { LOG.warn("unable to read policies from mdsal: ", e); 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 index 03b0f4f75..b6f045cdd 100644 --- 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 @@ -71,7 +71,7 @@ public class NextcloudProviderService extends AuthService { } @Override - protected UserTokenPayload requestUserRoles(String access_token, long expires_at) { + protected UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at) { // 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/providers/PemUtils.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/PemUtils.java new file mode 100644 index 000000000..fac46f6b1 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/PemUtils.java @@ -0,0 +1,106 @@ +/* + * ============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 java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; + +public class PemUtils { + + private static byte[] parsePEMFile(File pemFile) throws IOException { + if (!pemFile.isFile() || !pemFile.exists()) { + throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath())); + } + return parsePEMFile(new FileReader(pemFile)); + } + private static byte[] parsePEMFile(Reader inputReader) throws IOException { + PemReader reader = new PemReader(inputReader); + PemObject pemObject = reader.readPemObject(); + byte[] content = pemObject.getContent(); + reader.close(); + return content; + } + private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) { + PublicKey publicKey = null; + try { + KeyFactory kf = KeyFactory.getInstance(algorithm); + EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + publicKey = kf.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + System.out.println("Could not reconstruct the public key, the given algorithm could not be found."); + } catch (InvalidKeySpecException e) { + System.out.println("Could not reconstruct the public key"); + } + + return publicKey; + } + + private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) { + PrivateKey privateKey = null; + try { + KeyFactory kf = KeyFactory.getInstance(algorithm); + EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + privateKey = kf.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException e) { + System.out.println("Could not reconstruct the private key, the given algorithm could not be found."); + } catch (InvalidKeySpecException e) { + System.out.println("Could not reconstruct the private key"); + } + + return privateKey; + } + + public static PublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException { + byte[] bytes = PemUtils.parsePEMFile(new File(filepath)); + return PemUtils.getPublicKey(bytes, algorithm); + } + + public static PublicKey readPublicKey(String filecontent, String algorithm) throws IOException { + byte[] bytes = PemUtils.parsePEMFile(new StringReader(filecontent)); + return PemUtils.getPublicKey(bytes, algorithm); + } + + public static PrivateKey readPrivateKeyFromFile(String filepath, String algorithm) throws IOException { + byte[] bytes = PemUtils.parsePEMFile(new File(filepath)); + return PemUtils.getPrivateKey(bytes, algorithm); + } + + public static PrivateKey readPrivateKey(String filecontent, String algorithm) throws IOException { + byte[] bytes = PemUtils.parsePEMFile(new StringReader(filecontent)); + return PemUtils.getPrivateKey(bytes, algorithm); + } + +}
\ 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/RSAKeyReader.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/RSAKeyReader.java new file mode 100644 index 000000000..028dff9dd --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/RSAKeyReader.java @@ -0,0 +1,47 @@ +/* + * ============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 java.io.IOException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +public class RSAKeyReader { + + private static final String PREFIX_FILEURL = "file://"; + + public static RSAPrivateKey getPrivateKey(String filenameOrContent) throws IOException { + if (filenameOrContent.startsWith(PREFIX_FILEURL)) { + return (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(filenameOrContent.substring(PREFIX_FILEURL.length()), + "RSA"); + } + return (RSAPrivateKey) PemUtils.readPrivateKey(filenameOrContent, "RSA"); + } + + public static RSAPublicKey getPublicKey(String filenameOrContent) throws IOException { + if (filenameOrContent.startsWith(PREFIX_FILEURL)) { + return (RSAPublicKey) PemUtils.readPublicKeyFromFile(filenameOrContent.substring(PREFIX_FILEURL.length()), + "RSA"); + } + return (RSAPublicKey) PemUtils.readPublicKey(filenameOrContent, "RSA"); + } +} 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 index c2515e2b9..238f888bb 100644 --- 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 @@ -27,56 +27,96 @@ 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.io.IOException; +import java.security.Security; import java.util.Arrays; import java.util.Date; import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.authc.BearerToken; +import org.bouncycastle.jce.provider.BouncyCastleProvider; 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.apache.shiro.authc.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 * 1000L; private final String issuer; private static TokenCreator _instance; - private final String secret; + private final long tokenLifetimeSeconds; + private final Algorithm algorithm; 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(Config config) { + static { + Security.addProvider( + new BouncyCastleProvider() + ); + } + public static TokenCreator getInstance(Config config) throws IllegalArgumentException, IOException { if (_instance == null) { _instance = new TokenCreator(config); } return _instance; } - public static TokenCreator getInstance(String secret, String issuer) { + + public static TokenCreator getInstance(String alg, String secret, String issuer, long tokenLifetime) + throws IllegalArgumentException, IOException { + return getInstance(alg, secret, null, issuer, tokenLifetime); + } + + public static TokenCreator getInstance(String alg, String secret, String pubkey, String issuer, long tokenLifetime) + throws IllegalArgumentException, IOException { if (_instance == null) { - _instance = new TokenCreator(secret, issuer); + _instance = new TokenCreator(alg, secret, pubkey, issuer, tokenLifetime); } return _instance; } - private TokenCreator(Config config) { - this(config.getTokenSecret(),config.getTokenIssuer()); + private TokenCreator(Config config) throws IllegalArgumentException, IOException { + this(config.getAlgorithm(), config.getTokenSecret(), config.getPublicKey(), config.getTokenIssuer(), + config.getTokenLifetime()); } - private TokenCreator(String secret, String issuer) { - this.secret = secret; + + private TokenCreator(String alg, String secret, String pubkey, String issuer, long tokenLifetime) + throws IllegalArgumentException, IOException { this.issuer = issuer; + this.tokenLifetimeSeconds = tokenLifetime; + this.algorithm = this.createAlgorithm(alg, secret, pubkey); + } + + private Algorithm createAlgorithm(String alg, String secret, String pubkey) + throws IllegalArgumentException, IOException { + if(alg==null) { + alg = Config.TOKENALG_HS256; + } + switch (alg) { + case Config.TOKENALG_HS256: + return Algorithm.HMAC256(secret); + case Config.TOKENALG_RS256: + return Algorithm.RSA256(RSAKeyReader.getPublicKey(pubkey), RSAKeyReader.getPrivateKey(secret)); + case Config.TOKENALG_RS512: + return Algorithm.RSA512(RSAKeyReader.getPublicKey(pubkey), RSAKeyReader.getPrivateKey(secret)); + case Config.TOKENALG_CLIENT_RS256: + return Algorithm.RSA256(RSAKeyReader.getPublicKey(pubkey), null); + case Config.TOKENALG_CLIENT_RS512: + return Algorithm.RSA512(RSAKeyReader.getPublicKey(pubkey), null); + } + throw new IllegalArgumentException(String.format("unable to find algorithm for %s", alg)); + } public BearerToken createNewJWT(UserTokenPayload data) { - Algorithm algorithm = Algorithm.HMAC256(secret); final String token = JWT.create().withIssuer(issuer).withExpiresAt(new Date(data.getExp())) - .withSubject(data.getPreferredUsername()).withClaim(NAME_CLAIM, data.getGivenName()) - .withClaim(FAMILYNAME_CLAIM, data.getFamilyName()) + .withIssuedAt(new Date(data.getIat())).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); + .sign(this.algorithm); + LOG.trace("token created: {}", token); return new BearerToken(token); } @@ -84,8 +124,7 @@ public class TokenCreator { DecodedJWT jwt = null; LOG.debug("try to verify token {}", token); try { - Algorithm algorithm = Algorithm.HMAC256(secret); - JWTVerifier verifier = JWT.require(algorithm).withIssuer(issuer).build(); + JWTVerifier verifier = JWT.require(this.algorithm).withIssuer(issuer).build(); jwt = verifier.verify(token); } catch (JWTVerificationException e) { @@ -95,13 +134,17 @@ public class TokenCreator { } public long getDefaultExp() { - return new Date().getTime() + DEFAULT_TOKEN_LIFETIME_MS; + return new Date().getTime() + (this.tokenLifetimeSeconds * 1000); } public long getDefaultExp(long expIn) { return new Date().getTime() + expIn; } + public long getDefaultIat() { + return new Date().getTime(); + } + public UserTokenPayload decode(HttpServletRequest req) throws JWTDecodeException { final String authHeader = req.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer")) { @@ -117,4 +160,5 @@ public class TokenCreator { return data; } + } |