diff options
author | Michael DÜrre <michael.duerre@highstreet-technologies.com> | 2021-02-17 08:48:52 +0100 |
---|---|---|
committer | Michael DÜrre <michael.duerre@highstreet-technologies.com> | 2021-02-17 08:49:05 +0100 |
commit | f9486b50bbf6f92a4549203c1ede21ba912989c0 (patch) | |
tree | ea723a384f06a5186f61db2f3f59c012b563f7bd /sdnr/wt | |
parent | 8515052e1a6de2de56effbc61c73d3aa80169a93 (diff) |
add unit tests for oauth-provider
add unit tests and some fixes for oauth provider
Issue-ID: CCSDK-3169
Signed-off-by: Michael DÜrre <michael.duerre@highstreet-technologies.com>
Change-Id: I88c5b2123df7e0c6e49b087c3cc7d24130be5bff
Diffstat (limited to 'sdnr/wt')
34 files changed, 2880 insertions, 147 deletions
diff --git a/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/yangtools/mapperextensions/YangToolsBuilderAnnotationIntrospector.java b/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/yangtools/mapperextensions/YangToolsBuilderAnnotationIntrospector.java index 0a119e0a7..eb611c602 100644 --- a/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/yangtools/mapperextensions/YangToolsBuilderAnnotationIntrospector.java +++ b/sdnr/wt/data-provider/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/yangtools/mapperextensions/YangToolsBuilderAnnotationIntrospector.java @@ -127,4 +127,8 @@ public class YangToolsBuilderAnnotationIntrospector extends JacksonAnnotationInt } + public void addDeserializer(Class<?> clazz, String builder) { + this.customDeserializer.put(clazz, builder); + } + } diff --git a/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestAbout.java b/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestAbout.java index 624451ca7..48e512ad9 100644 --- a/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestAbout.java +++ b/sdnr/wt/data-provider/provider/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/test/TestAbout.java @@ -32,8 +32,6 @@ import java.nio.file.Files; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.jline.utils.Log; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -111,7 +109,7 @@ public class TestAbout { AboutHelperServlet sv = new AboutHelperServlet(); assertNotNull(sv.getGroupIdOrDefault(null)); } - + private class AboutHelperServlet extends AboutHttpServlet { diff --git a/sdnr/wt/oauth-provider/provider-jar/pom.xml b/sdnr/wt/oauth-provider/provider-jar/pom.xml index 3ee21b2aa..d669a1636 100644 --- a/sdnr/wt/oauth-provider/provider-jar/pom.xml +++ b/sdnr/wt/oauth-provider/provider-jar/pom.xml @@ -151,6 +151,11 @@ </dependency> <dependency> <groupId>org.opendaylight.mdsal</groupId> + <artifactId>mdsal-binding-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.opendaylight.mdsal</groupId> <artifactId>yang-binding</artifactId> <scope>provided</scope> </dependency> @@ -174,5 +179,31 @@ <artifactId>jetty-servlet</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-data-provider-provider</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.opendaylight.mdsal.binding.model.ietf</groupId> + <artifactId>rfc6991-ietf-yang-types</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.opendaylight.netconf</groupId> + <artifactId>sal-netconf-connector</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</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 index a3e4bcc2a..0a40e8ddc 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 @@ -50,9 +50,8 @@ public class OAuth2Realm extends TokenAuthRealm { super(); super.setName(REALM_NAME); this.config = Config.getInstance(); - this.tokenCreator = TokenCreator.getInstance(); + this.tokenCreator = TokenCreator.getInstance(this.config); LOG.info("instantiated"); - } @Override 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 ba26106c9..a71f4c7dc 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 @@ -48,16 +48,16 @@ public class Config { private List<OAuthProviderConfig> providers; - private String host; private String redirectUri; private String supportOdlUsers; private String tokenSecret; private String tokenIssuer; + private String publicUrl; @Override public String toString() { - return "Config [providers=" + providers + ", host=" + host + ", redirectUri=" + redirectUri + return "Config [providers=" + providers + ", redirectUri=" + redirectUri + ", supportOdlUsers=" + supportOdlUsers + ", tokenSecret=" + tokenSecret + ", tokenIssuer=" + tokenIssuer + "]"; } @@ -72,14 +72,6 @@ public class Config { this.providers = providers; } - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - public String getRedirectUri() { return redirectUri; } @@ -112,6 +104,15 @@ public class Config { this.tokenIssuer = tokenIssuer; } + + public String getPublicUrl() { + return publicUrl; + } + + public void setPublicUrl(String publicUrl) { + this.publicUrl = publicUrl; + } + @JsonIgnore private void handleEnvironmentVars() { if (isEnvExpression(tokenIssuer)) { @@ -120,8 +121,8 @@ public class Config { if (isEnvExpression(tokenSecret)) { this.tokenSecret = getProperty(tokenSecret, null); } - if (isEnvExpression(host)) { - this.host = getProperty(host, null); + if (isEnvExpression(publicUrl)) { + this.publicUrl = getProperty(publicUrl, null); } if (isEnvExpression(redirectUri)) { this.redirectUri = getProperty(redirectUri, null); @@ -139,9 +140,12 @@ public class Config { if (tokenSecret == null || tokenSecret.isEmpty()) { this.tokenSecret = DEFAULT_TOKENSECRET; } - if (redirectUri == null || redirectUri.isEmpty()) { + if (redirectUri == null || redirectUri.isEmpty() || "null".equals(redirectUri)) { this.redirectUri = DEFAULT_REDIRECTURI; } + if (publicUrl != null && (publicUrl.isEmpty() || "null".equals(publicUrl))) { + this.publicUrl = null; + } if (supportOdlUsers == null || supportOdlUsers.isEmpty()) { this.supportOdlUsers = DEFAULT_SUPPORTODLUSERS; } @@ -150,11 +154,12 @@ public class Config { static boolean isEnvExpression(String key) { return key != null && key.contains(ENVVARIABLE); } - - private static String generateSecret() { + public static String generateSecret() { + return generateSecret(30); + } + public static String generateSecret(int targetStringLength) { int leftLimit = 48; // numeral '0' int rightLimit = 122; // letter 'z' - int targetStringLength = 30; Random random = new Random(); String generatedString = random.ints(leftLimit, rightLimit + 1) @@ -226,10 +231,12 @@ public class Config { } - public static Config getInstance() throws IOException { + return getInstance(DEFAULT_CONFIGFILENAME); + } + public static Config getInstance(String filename) throws IOException { if(_instance==null) { - _instance = load(DEFAULT_CONFIGFILENAME); + _instance = load(filename); } return _instance; } 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 index e4d67432f..3f1673c93 100644 --- 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 @@ -22,35 +22,39 @@ package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data; import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.HashMap; +import java.util.Map; import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.OAuthProviderFactory.OAuthProvider; public class OAuthProviderConfig { - private String host; + private String url; private String clientId; private String secret; private String id; private String title; private String scope; private OAuthProvider type; + private Map<String,String> roleMapping; public OAuthProvider getType() { return type; } - public OAuthProviderConfig(String id, String host, String clientId, String secret, String scope, + public OAuthProviderConfig(String id, String url, String clientId, String secret, String scope, String title) { this.id = id; - this.host = host; + this.url = url; this.clientId = clientId; this.secret = secret; this.scope = scope; this.title = title; + this.roleMapping = new HashMap<>(); } @Override public String toString() { - return "OAuthProviderConfig [host=" + host + ", clientId=" + clientId + ", secret=" + secret + ", id=" + id + return "OAuthProviderConfig [host=" + url + ", clientId=" + clientId + ", secret=" + secret + ", id=" + id + ", title=" + title + ", scope=" + scope + ", type=" + type + "]"; } @@ -62,8 +66,8 @@ public class OAuthProviderConfig { this(null, null, null, null, null, null); } - public void setHost(String host) { - this.host = host; + public void setUrl(String url) { + this.url = url; } public void setClientId(String clientId) { @@ -90,8 +94,8 @@ public class OAuthProviderConfig { return this.id; } - public String getHost() { - return this.host; + public String getUrl() { + return this.url; } public String getClientId() { @@ -110,13 +114,21 @@ public class OAuthProviderConfig { return this.scope; } + public Map<String, String> getRoleMapping() { + return roleMapping; + } + + public void setRoleMapping(Map<String, String> roleMapping) { + this.roleMapping = roleMapping; + } + @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(url)) { + this.url = Config.getProperty(url, null); } if (Config.isEnvExpression(clientId)) { this.clientId = Config.getProperty(clientId, null); 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 index 28ef3b3b0..19eb4b68e 100644 --- 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 @@ -68,7 +68,7 @@ public class OdlPolicy { private boolean patch; public PolicyMethods() { - + this(false, false, false, false, false); } public PolicyMethods(boolean get, boolean post, boolean put, boolean del, boolean patch) { 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 f5d344d41..cd4239081 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 @@ -48,11 +48,12 @@ 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.MdSalAuthorizationStore; 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.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; import org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619.shiro.configuration.Urls; @@ -68,11 +69,14 @@ public class AuthHttpServlet extends HttpServlet { //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 REDIRECTURI_FORMAT = REDIRECTURI + "/%s"; 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 String LOGIN_REDIRECT_REGEX = "^\\" + LOGINURI + "\\/([^\\/]+)$"; //private static final Pattern PROVIDERID_PATTERN = Pattern.compile(PROVIDERID_REGEX); private static final Pattern REDIRECTID_PATTERN = Pattern.compile(REDIRECTID_REGEX); + private static final Pattern LOGIN_REDIRECT_PATTERN = Pattern.compile(LOGIN_REDIRECT_REGEX); private static final String DEFAULT_DOMAIN = "sdn"; private static final String HEAEDER_AUTHORIZATION = "Authorization"; @@ -83,6 +87,7 @@ public class AuthHttpServlet extends HttpServlet { "org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter2"; private static final String CLASSNAME_ODLMDSALAUTH = "org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter"; + public static final String LOGIN_REDIRECT_FORMAT = LOGINURI + "/%s"; private final ObjectMapper mapper; /* state <=> AuthProviderService> */ @@ -92,15 +97,17 @@ public class AuthHttpServlet extends HttpServlet { private final TokenCreator tokenCreator; private final Config config; private ShiroConfiguration shiroConfiguration; + private DataBroker dataBroker; + private MdSalAuthorizationStore mdsalAuthStore; public AuthHttpServlet() throws IOException { - this.tokenCreator = TokenCreator.getInstance(); this.config = Config.getInstance(); + this.tokenCreator = TokenCreator.getInstance(this.config); 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())); + this.config.getRedirectUri(), TokenCreator.getInstance(this.config))); } } @@ -117,12 +124,19 @@ public class AuthHttpServlet extends HttpServlet { this.shiroConfiguration = shiroConfiguration; } + public void setDataBroker(DataBroker dataBroker) { + this.dataBroker = dataBroker; + this.mdsalAuthStore = new MdSalAuthorizationStore(this.dataBroker); + } + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { LOG.debug("GET request for {}", req.getRequestURI()); - fillHost(req); + getHost(req); if (PROVIDERSURI.equals(req.getRequestURI())) { this.sendResponse(resp, HttpServletResponse.SC_OK, getConfigs(this.providerStore.values())); + } else if (req.getRequestURI().startsWith(LOGINURI)) { + this.handleLoginRedirect(req, resp); } else if (POLICIESURI.equals(req.getRequestURI())) { this.sendResponse(resp, HttpServletResponse.SC_OK, this.getPoliciesForUser(req)); } else if (req.getRequestURI().startsWith(REDIRECTURI)) { @@ -133,6 +147,22 @@ public class AuthHttpServlet extends HttpServlet { } + private void handleLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException { + final String uri = req.getRequestURI(); + final Matcher matcher = LOGIN_REDIRECT_PATTERN.matcher(uri); + if (matcher.find()) { + final String id = matcher.group(1); + AuthService provider = this.providerStore.getOrDefault(id, null); + if (provider != null) { + //provider.setLocalHostUrl(getHost(req)); + String redirectUrl = getHost(req) + String.format(REDIRECTURI_FORMAT, id); + provider.sendLoginRedirectResponse(resp, redirectUrl); + return; + } + } + this.sendResponse(resp, HttpServletResponse.SC_NOT_FOUND, ""); + } + /** * find out what urls can be accessed by user and which are forbidden * @@ -161,8 +191,13 @@ public class AuthHttpServlet extends HttpServlet { try { final String authClass = getAuthClass(matcher.group(1)); Optional<OdlPolicy> policy = Optional.empty(); - if (authClass.equals(CLASSNAME_ODLBASICAUTH) - || authClass.equals(CLASSNAME_ODLBEARERANDBASICAUTH)) { + //anon access allowed + if (authClass == null) { + policy = Optional.of(OdlPolicy.allowAll(urlRule.getPairKey())); + } else if (authClass.equals(CLASSNAME_ODLBASICAUTH)) { + policy = isBasic(req) ? this.getTokenBasedPolicy(urlRule, matcher, data) + : Optional.of(OdlPolicy.denyAll(urlRule.getPairKey())); + } else if (authClass.equals(CLASSNAME_ODLBEARERANDBASICAUTH)) { policy = this.getTokenBasedPolicy(urlRule, matcher, data); } else if (authClass.equals(CLASSNAME_ODLMDSALAUTH)) { policy = this.getMdSalBasedPolicy(urlRule, matcher, data); @@ -172,6 +207,7 @@ public class AuthHttpServlet extends HttpServlet { } else { LOG.warn("unable to get policy for authClass {} for entry {}", authClass, urlRule.getPairValue()); + policies.add(OdlPolicy.denyAll(urlRule.getPairKey())); } } catch (NoDefinitionFoundException e) { LOG.warn("unknown authClass: ", e); @@ -188,20 +224,24 @@ public class AuthHttpServlet extends HttpServlet { } /** - * extract policy rule for user from MD-SAL - * not yet supported + * 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) { - + if (this.mdsalAuthStore != null) { + return data != null ? this.mdsalAuthStore.getPolicy(urlRule.getPairKey(), data.getRoles()) + : Optional.of(OdlPolicy.denyAll(urlRule.getPairKey())); + } return Optional.empty(); } /** * extract policy rule for user from url rules of config + * * @param urlRule * @param matcher * @param data @@ -209,25 +249,27 @@ public class AuthHttpServlet extends HttpServlet { */ private Optional<OdlPolicy> getTokenBasedPolicy(Urls urlRule, Matcher matcher, UserTokenPayload data) { final String url = urlRule.getPairKey(); - if (!urlRule.getPairValue().contains(",")) { + final String rule = urlRule.getPairValue(); + if (!rule.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) { + } + 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)); + return Optional.of(OdlPolicy.allowAll(url)); } else { - Optional.of(OdlPolicy.denyAll(url)); + return 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)); + return Optional.of(OdlPolicy.allowAll(url)); } else { - Optional.of(OdlPolicy.denyAll(url)); + return Optional.of(OdlPolicy.denyAll(url)); } } else { LOG.warn("unable to detect url role value: {}", urlRule.getPairValue()); @@ -252,7 +294,7 @@ public class AuthHttpServlet extends HttpServlet { private UserTokenPayload getUserInfo(HttpServletRequest req) { if (isBearer(req)) { - UserTokenPayload data = TokenCreator.getInstance().decode(req); + UserTokenPayload data = TokenCreator.getInstance(this.config).decode(req); if (data != null) { return data; } @@ -317,8 +359,8 @@ public class AuthHttpServlet extends HttpServlet { } - private void fillHost(HttpServletRequest req) { - String hostUrl = this.config.getHost(); + public String getHost(HttpServletRequest req) { + String hostUrl = this.config.getPublicUrl(); if (hostUrl == null) { final String tmp = req.getRequestURL().toString(); final String regex = "^(http[s]{0,1}:\\/\\/[^\\/]+)"; @@ -326,16 +368,17 @@ public class AuthHttpServlet extends HttpServlet { final Matcher matcher = pattern.matcher(tmp); if (matcher.find()) { hostUrl = matcher.group(1); - this.config.setHost(hostUrl); } } + LOG.info("host={}", hostUrl); + return hostUrl; } private List<PublicOAuthProviderConfig> getConfigs(Collection<AuthService> values) { List<PublicOAuthProviderConfig> configs = new ArrayList<>(); for (AuthService svc : values) { - configs.add(svc.getConfig(this.config.getHost())); + configs.add(svc.getConfig()); } return configs; } @@ -353,8 +396,8 @@ public class AuthHttpServlet extends HttpServlet { if (matcher.find()) { AuthService provider = this.providerStore.getOrDefault(matcher.group(1), null); if (provider != null) { - provider.setLocalHostUrl(this.config.getHost()); - provider.handleRedirect(req, resp); + //provider.setLocalHostUrl(getHost(req)); + provider.handleRedirect(req, resp, getHost(req)); return; } } @@ -382,33 +425,6 @@ public class AuthHttpServlet extends HttpServlet { 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); @@ -416,12 +432,6 @@ public class AuthHttpServlet extends HttpServlet { 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); 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 ec685a003..3cb79757c 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 @@ -33,9 +33,11 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.stream.Collectors; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -52,16 +54,13 @@ import org.slf4j.LoggerFactory; public abstract class AuthService { - private static final Logger LOG = LoggerFactory.getLogger(AuthService.class.getName()); + private static final Logger LOG = LoggerFactory.getLogger(AuthService.class); 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(); @@ -75,23 +74,31 @@ public abstract class AuthService { protected abstract String getLoginUrl(String callbackUrl); + protected abstract UserTokenPayload requestUserRoles(String access_token, long expires_at); + + protected abstract boolean verifyState(String state); + 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()); + this.httpClient = new MappingBaseHttpClient(this.config.getUrl()); } - public PublicOAuthProviderConfig getConfig(String host) { - return new PublicOAuthProviderConfig(this, host); + public PublicOAuthProviderConfig getConfig() { + return new PublicOAuthProviderConfig(this); } - public void handleRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException { + protected MappingBaseHttpClient getHttpClient() { + return this.httpClient; + } + + public void handleRedirect(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException { switch (this.getResponseType()) { case CODE: - this.handleRedirectCode(req, resp); + this.handleRedirectCode(req, resp, host); break; case TOKEN: sendErrorResponse(resp, "not yet implemented"); @@ -99,39 +106,56 @@ public abstract class AuthService { case SESSION_STATE: break; } + } + public void sendLoginRedirectResponse(HttpServletResponse resp, String callbackUrl) { + resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + resp.setHeader("Location", this.getLoginUrl(callbackUrl)); } 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 { + private void handleRedirectCode(HttpServletRequest req, HttpServletResponse resp, String host) throws IOException { final String code = req.getParameter("code"); - OAuthResponseData response = this.getTokenForUser(code); + final String state = req.getParameter("state"); + OAuthResponseData response = null; + if(this.verifyState(state)) { + response = this.getTokenForUser(code, host); + } if (response != null) { 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); + if (data != null) { + this.handleUserInfoToken(data, resp, host); + } else { + sendErrorResponse(resp, "unable to verify user"); + } } else { - this.handleUserInfoToken(response.getAccess_token(), resp); + this.handleUserInfoToken(response.getAccess_token(), resp, host); } } else { sendErrorResponse(resp, "unable to verify code"); } + } + private void handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl) + throws IOException { + BearerToken onapToken = this.tokenCreator.createNewJWT(data); + sendTokenResponse(resp, onapToken, localHostUrl); } - private void handleUserInfoToken(String accessToken, HttpServletResponse resp) throws IOException { + private void handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl) + throws IOException { try { DecodedJWT jwt = JWT.decode(accessToken); - String spayload = base64Decode(jwt.getPayload()); - LOG.debug("payload in jwt from keycload='{}'", spayload); - + LOG.debug("payload in jwt='{}'", spayload); UserTokenPayload data = this.mapAccessToken(spayload); - BearerToken onapToken = this.tokenCreator.createNewJWT(data); - sendTokenResponse(resp, onapToken); + this.handleUserInfoToken(data, resp, localHostUrl); } catch (JWTDecodeException | JsonProcessingException e) { LOG.warn("unable to decode jwt token {}: ", accessToken, e); sendErrorResponse(resp, e.getMessage()); @@ -139,7 +163,12 @@ public abstract class AuthService { } - private void sendTokenResponse(HttpServletResponse resp, BearerToken data) throws IOException { + protected List<String> mapRoles(List<String> roles) { + final Map<String, String> map = this.config.getRoleMapping(); + return roles.stream().map(r -> map.getOrDefault(r, r)).collect(Collectors.toList()); + } + + private void sendTokenResponse(HttpServletResponse resp, BearerToken data, String localHostUrl) throws IOException { if (this.redirectUri == null) { byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0]; resp.setStatus(200); @@ -150,7 +179,7 @@ public abstract class AuthService { os.write(output); } else { resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - resp.setHeader("Location", assembleUrl(this.localHostUrl, this.redirectUri, data.getToken())); + resp.setHeader("Location", assembleUrl(localHostUrl, this.redirectUri, data.getToken())); } } @@ -160,7 +189,7 @@ public abstract class AuthService { return new String(Base64.getDecoder().decode(data), StandardCharsets.UTF_8); } - private OAuthResponseData getTokenForUser(String code) { + private OAuthResponseData getTokenForUser(String code, String localHostUrl) { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/x-www-form-urlencoded"); @@ -168,15 +197,15 @@ public abstract class AuthService { 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())); + 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); + 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; } @@ -185,8 +214,6 @@ public abstract class AuthService { return null; } - - /** * Assemble callback url for service provider {host}{baseUri}/{serviceId} e.g. * http://10.20.0.11:8181/oauth/redirect/keycloak @@ -243,11 +270,10 @@ public abstract class AuthService { this.loginUrl = loginUrl; } - public PublicOAuthProviderConfig(AuthService authService, String host) { + public PublicOAuthProviderConfig(AuthService authService) { this.id = authService.config.getId(); this.title = authService.config.getTitle(); - this.loginUrl = authService.getLoginUrl( - assembleRedirectUrl(host, AuthHttpServlet.REDIRECTURI, this.id)); + this.loginUrl = String.format(AuthHttpServlet.LOGIN_REDIRECT_FORMAT, authService.config.getId()); } } 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 new file mode 100644 index 000000000..4a8bdfa1b --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/GitlabProviderService.java @@ -0,0 +1,173 @@ +/* + * ============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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.client.MappedBaseHttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GitlabProviderService extends AuthService { + + private static final Logger LOG = LoggerFactory.getLogger(GitlabProviderService.class); + private Map<String, String> additionalTokenVerifierParams; + protected final List<String> randomIds; + private static final String API_USER_URI = "/api/v4/user"; + private static final String API_GROUP_URI = "/api/v4/groups?min_access_level=10"; + + public GitlabProviderService(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) { + super(config, redirectUri, tokenCreator); + this.additionalTokenVerifierParams = new HashMap<>(); + this.additionalTokenVerifierParams.put("grant_type", "authorization_code"); + this.randomIds = new ArrayList<>(); + } + + @Override + protected String getTokenVerifierUri() { + return "/oauth/token"; + } + + @Override + protected String getLoginUrl(String callbackUrl) { + return String.format("%s/oauth/authorize?client_id=%s&response_type=code&state=%s&redirect_uri=%s", + this.config.getUrl(), urlEncode(this.config.getClientId()), this.createRandomId(), callbackUrl); + } + + private String createRandomId() { + String rnd = null; + while(true) { + rnd=Config.generateSecret(20); + if(!this.randomIds.contains(rnd)) { + break; + } + } + this.randomIds.add(rnd); + return rnd; + } + + @Override + protected ResponseType getResponseType() { + return ResponseType.CODE; + } + + @Override + protected Map<String, String> getAdditionalTokenVerifierParams() { + return this.additionalTokenVerifierParams; + + } + + @Override + protected boolean doSeperateRolesRequest() { + return true; + } + + @Override + protected UserTokenPayload mapAccessToken(String spayload) throws JsonMappingException, JsonProcessingException { + return null; + } + + @Override + protected UserTokenPayload requestUserRoles(String access_token, 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)); + Optional<MappedBaseHttpResponse<GitlabUserInfo>> userInfo = + this.getHttpClient().sendMappedRequest(API_USER_URI, "GET", null, authHeaders, GitlabUserInfo.class); + if (userInfo.isEmpty()) { + LOG.warn("unable to read user data"); + return null; + } + Optional<MappedBaseHttpResponse<GitlabGroupInfo[]>> groupInfos = this.getHttpClient() + .sendMappedRequest(API_GROUP_URI, "GET", null, authHeaders, GitlabGroupInfo[].class); + if (groupInfos.isEmpty()) { + LOG.warn("unable to read group information for user"); + return null; + } + UserTokenPayload data = new UserTokenPayload(); + GitlabUserInfo uInfo = userInfo.get().body; + data.setPreferredUsername(uInfo.getUsername()); + data.setGivenName(uInfo.getName()); + data.setFamilyName(uInfo.getName()); + data.setExp(expires_at); + List<String> roles = new ArrayList<>(); + GitlabGroupInfo[] uRoles = groupInfos.get().body; + for (GitlabGroupInfo uRole : uRoles) { + roles.add(uRole.getName()); + } + data.setRoles(this.mapRoles(roles)); + return data; + } + + + + @SuppressWarnings("unused") + private static class GitlabUserInfo { + + private String username; + private String name; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + @SuppressWarnings("unused") + private static class GitlabGroupInfo { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + @Override + protected boolean verifyState(String state) { + if(this.randomIds.contains(state)) { + this.randomIds.remove(state); + return true; + } + return false; + } +} 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 3bfbb3b25..86383c983 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 @@ -51,17 +51,16 @@ public class KeycloakProviderService extends AuthService { 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(), + this.config.getUrl(), urlEncode(this.config.getClientId()), this.config.getScope(), urlEncode(callbackUrl)); } - - - private List<String> mapRoles(List<String> data) { - + @Override + protected List<String> mapRoles(List<String> data) { + final Map<String,String> map = this.config.getRoleMapping(); List<String> filteredRoles = data.stream().filter(role -> !role.equals("uma_authorization") && !role.equals("offline_access")) - .map(r -> r).collect(Collectors.toList()); + .map(r -> map.getOrDefault(r, r)).collect(Collectors.toList()); return filteredRoles; } @@ -93,5 +92,15 @@ public class KeycloakProviderService extends AuthService { return data; } + @Override + protected UserTokenPayload requestUserRoles(String access_token, long expires_at) { + return null; + } + + @Override + protected boolean verifyState(String state) { + return true; + } + } 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 new file mode 100644 index 000000000..b181af040 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/MdSalAuthorizationStore.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 java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OdlPolicy; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OdlPolicy.PolicyMethods; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.Policies; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions.Actions; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MdSalAuthorizationStore { + + private static final Logger LOG = LoggerFactory.getLogger(MdSalAuthorizationStore.class.getName()); + + private final DataBroker dataBroker; + + public MdSalAuthorizationStore(DataBroker dataBroker) { + this.dataBroker = dataBroker; + } + + 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 { + odata = this.dataBroker.newReadOnlyTransaction().read(LogicalDatastoreType.CONFIGURATION, iif).get(); + } catch (InterruptedException | ExecutionException e) { + LOG.warn("unable to read policies from mdsal: ", e); + } + if (odata.isEmpty()) { + return Optional.empty(); + } + + Optional<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies> entry = + odata.get().getPolicies().stream().filter((e) -> path.equals(e.getResource())).findFirst(); + if (entry.isEmpty()) { + return Optional.empty(); + } + List<Permissions> permissions = entry.get().getPermissions(); + if (permissions == null) { + return Optional.empty(); + } + Optional<Permissions> rolePm = permissions.stream().filter((e) -> userRoles.contains(e.getRole())).findFirst(); + if (rolePm.isEmpty()) { + return Optional.empty(); + } + return Optional.of(mapPolicy(path, rolePm.get().getActions())); + } + + private OdlPolicy mapPolicy(String path, List<Actions> actions) { + PolicyMethods methods = new PolicyMethods(); + String action; + for (Actions a : actions) { + action = a.getName().toLowerCase(); + switch (action) { + case "get": + methods.setGet(true); + break; + case "post": + methods.setPost(true); + break; + case "put": + methods.setPut(true); + break; + case "delete": + methods.setDelete(true); + break; + case "patch": + methods.setPatch(true); + break; + default: + LOG.warn("unknown http method {}",action); + break; + } + } + return new OdlPolicy(path, methods); + } + +} 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 ad1da68b3..03b0f4f75 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 @@ -70,4 +70,16 @@ public class NextcloudProviderService extends AuthService { return null; } + @Override + protected UserTokenPayload requestUserRoles(String access_token, long expires_at) { + // TODO Auto-generated method stub + return null; + } + + @Override + protected boolean verifyState(String state) { + // TODO Auto-generated method stub + 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/providers/OAuthProviderFactory.java b/sdnr/wt/oauth-provider/provider-jar/src/main/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/providers/OAuthProviderFactory.java index a09f150ef..193e7a7f7 100644 --- 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 @@ -33,12 +33,14 @@ public class OAuthProviderFactory { return new KeycloakProviderService(config, redirectUri, tokenCreator); case NEXTCLOUD: return new NextcloudProviderService(config, redirectUri, tokenCreator); + case GITLAB: + return new GitlabProviderService(config, redirectUri, tokenCreator); } return null; } public static enum OAuthProvider { - KEYCLOAK, NEXTCLOUD + KEYCLOAK, NEXTCLOUD, GITLAB } } 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 3244f90e5..e7e9b72f9 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 @@ -41,28 +41,38 @@ 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 final String issuer; private static TokenCreator _instance; - private static final String SECRET = Config.getProperty("${TOKEN_SECRET}", "secret"); + private final String 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() { + public static TokenCreator getInstance(Config config) { if (_instance == null) { - _instance = new TokenCreator(); + _instance = new TokenCreator(config); + } + return _instance; + } + public static TokenCreator getInstance(String secret, String issuer) { + if (_instance == null) { + _instance = new TokenCreator(secret, issuer); } return _instance; } - private TokenCreator() { - + private TokenCreator(Config config) { + this(config.getTokenSecret(),config.getTokenIssuer()); + } + private TokenCreator(String secret, String issuer) { + this.secret = secret; + this.issuer = issuer; } public BearerToken createNewJWT(UserTokenPayload data) { - Algorithm algorithm = Algorithm.HMAC256(SECRET); - final String token = JWT.create().withIssuer(TOKEN_ISSUER).withExpiresAt(new Date(data.getExp())) + 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()) .withArrayClaim(ROLES_CLAIM, data.getRoles().toArray(new String[data.getRoles().size()])) @@ -74,8 +84,8 @@ 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(TOKEN_ISSUER).build(); + Algorithm algorithm = Algorithm.HMAC256(secret); + JWTVerifier verifier = JWT.require(algorithm).withIssuer(issuer).build(); jwt = verifier.verify(token); } catch (JWTVerificationException e) { @@ -88,6 +98,10 @@ public class TokenCreator { return new Date().getTime() + DEFAULT_TOKEN_LIFETIME_MS; } + public long getDefaultExp(long exp_in) { + return new Date().getTime() + exp_in; + } + public UserTokenPayload decode(HttpServletRequest req) throws JWTDecodeException { final String authHeader = req.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer")) { diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestAuthHttpServlet.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestAuthHttpServlet.java new file mode 100644 index 000000000..bf2a39b55 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestAuthHttpServlet.java @@ -0,0 +1,368 @@ +/* + * ============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.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.google.common.util.concurrent.FluentFuture; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.jolokia.osgi.security.Authenticator; +import org.json.JSONArray; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.common.http.BaseHTTPClient; +import org.onap.ccsdk.features.sdnr.wt.common.test.ServletOutputStreamToByteArrayOutputStream; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.CustomObjectMapper; +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.http.AuthHttpServlet; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.http.HeadersOnlyHttpServletRequest; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.test.helper.OdlJsonMapper; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.test.helper.OdlXmlMapper; +import org.opendaylight.aaa.api.IdMService; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.ReadTransaction; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +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.ShiroConfigurationBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.Policies; +import org.opendaylight.yangtools.util.concurrent.FluentFutures; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + + +public class TestAuthHttpServlet { + + private static final String TESTCONFIGFILE = TestConfig.TEST_CONFIG_FILENAME; + private static final String TESTSHIROCONFIGFILE = "src/test/resources/aaa-app-config.test.xml"; + private static final String MDSALDYNAUTHFILENAME = "src/test/resources/mdsalDynAuthData.json"; + private static TestServlet servlet; + private static DataBroker dataBroker = loadDynamicMdsalAuthDataBroker(); + private static Authenticator odlAuthenticator = mock(Authenticator.class); + private static IdMService odlIdentityService = mock(IdMService.class); + private static ShiroConfiguration shiroConfiguration = null; + private static TokenCreator tokenCreator; +// private static final HttpServletRequest authreq = new HeadersOnlyHttpServletRequest( +// Map.of("Authorization", BaseHTTPClient.getAuthorizationHeaderValue("admin@sdn", "admin"))); + + @BeforeClass + public static void init() { + + try { + Config config = createConfigFile(); + tokenCreator = TokenCreator.getInstance(config); + servlet = new TestServlet(); + shiroConfiguration = loadShiroConfig(TESTSHIROCONFIGFILE); + } catch (IOException e) { + fail(e.getMessage()); + } + servlet.setDataBroker(dataBroker); + servlet.setOdlAuthenticator(odlAuthenticator); + servlet.setOdlIdentityService(odlIdentityService); + servlet.setShiroConfiguration(shiroConfiguration); + } + + private static DataBroker loadDynamicMdsalAuthDataBroker() { + DataBroker dataBroker = mock(DataBroker.class); + ReadTransaction rotx = mock(ReadTransaction.class); + InstanceIdentifier<Policies> iif = InstanceIdentifier.create(HttpAuthorization.class).child(Policies.class); + try { + when(rotx.read(LogicalDatastoreType.CONFIGURATION, iif)) + .thenReturn(loadDataBrokerFile(MDSALDYNAUTHFILENAME, Policies.class)); + } catch (IOException e) { + fail("problem init databroker read" + e.getMessage()); + } + when(dataBroker.newReadOnlyTransaction()).thenReturn(rotx); + return dataBroker; + } + + private static <T> FluentFuture<Optional<T>> loadDataBrokerFile(String fn, Class<T> clazz) throws IOException { + return FluentFutures.immediateFluentFuture(Optional.ofNullable(readJson(new File(fn), clazz))); + } + + private static ShiroConfiguration loadShiroConfig(String filename) + throws JsonParseException, JsonMappingException, IOException { + OdlXmlMapper mapper = new OdlXmlMapper(); + return mapper.readValue(new File(filename), ShiroConfigurationBuilder.class).build(); + } + + private static Config createConfigFile() throws IOException { + return Config.getInstance(TESTCONFIGFILE); + + } + + @Test + public void testValidLoginRedirect() { + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/login/keycloak"); + HttpServletResponse resp = mock(HttpServletResponse.class); + try { + servlet.doGet(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(302); + verify(resp).setHeader("Location", + "http://10.20.11.160:8080/auth/realms/onap/protocol/openid-connect/auth?client_id=odlux.app&response" + + "_type=code&scope=openid&redirect_uri=http%3A%2F%2Fnasp.diasf.de%2Foauth%2Fredirect%2Fkeycloak"); + } + + @Test + public void testInValidLoginRedirect() { + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/login/unknownproviderid"); + HttpServletResponse resp = mock(HttpServletResponse.class); + ServletOutputStreamToByteArrayOutputStream printOut = new ServletOutputStreamToByteArrayOutputStream(); + try { + when(resp.getOutputStream()).thenReturn(printOut); + servlet.doGet(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(404); + } + + @Test + public void testValidLogin() { + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/login"); + when(req.getParameter("username")).thenReturn("admin"); + when(req.getParameter("password")).thenReturn("admin"); + when(odlAuthenticator.authenticate(any(HeadersOnlyHttpServletRequest.class))).thenReturn(true); + HttpServletResponse resp = mock(HttpServletResponse.class); + ServletOutputStreamToByteArrayOutputStream printOut = new ServletOutputStreamToByteArrayOutputStream(); + try { + when(resp.getOutputStream()).thenReturn(printOut); + servlet.doPost(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(200); + } + + @Test + public void testGetProviders() { + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/providers"); + HttpServletResponse resp = mock(HttpServletResponse.class); + ServletOutputStreamToByteArrayOutputStream printOut = new ServletOutputStreamToByteArrayOutputStream(); + try { + when(resp.getOutputStream()).thenReturn(printOut); + servlet.doGet(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(200); + String responseBody = printOut.getByteArrayOutputStream().toString(StandardCharsets.UTF_8); + System.out.println(responseBody); + JSONArray a = new JSONArray(responseBody); + assertEquals(1, a.length()); + assertEquals("keycloak", a.getJSONObject(0).getString("id")); + assertEquals("OSNL Keycloak Provider", a.getJSONObject(0).getString("title")); + assertEquals("/oauth/login/keycloak", a.getJSONObject(0).getString("loginUrl")); + + } + + @Test + public void testPoliciesAnon() { + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/policies"); + HttpServletResponse resp = mock(HttpServletResponse.class); + ServletOutputStreamToByteArrayOutputStream printOut = new ServletOutputStreamToByteArrayOutputStream(); + try { + when(resp.getOutputStream()).thenReturn(printOut); + servlet.doGet(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(200); + String responseBody = printOut.getByteArrayOutputStream().toString(StandardCharsets.UTF_8); + System.out.println(responseBody); + OdlPolicy[] anonPolicies = null; + try { + anonPolicies = readJson(responseBody, OdlPolicy[].class); + } catch (JsonProcessingException e) { + fail("unable to read anon policies response"); + } + assertEquals(9, anonPolicies.length); + OdlPolicy pApidoc = find(anonPolicies, "/apidoc/**"); + assertNotNull(pApidoc); + assertAllEquals(false, pApidoc); + OdlPolicy pOauth = find(anonPolicies, "/oauth/**"); + assertNotNull(pOauth); + assertAllEquals(true, pOauth); + OdlPolicy pRestconf = find(anonPolicies, "/rests/**"); + assertNotNull(pRestconf); + assertAllEquals(false, pRestconf); + } + + @Test + public void testPoliciesBasicAuth() { + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/policies"); + when(req.getHeader("Authorization")).thenReturn(BaseHTTPClient.getAuthorizationHeaderValue("admin", "admin")); + when(odlIdentityService.listRoles("admin@sdn", "sdn")).thenReturn(Arrays.asList("admin")); + HttpServletResponse resp = mock(HttpServletResponse.class); + ServletOutputStreamToByteArrayOutputStream printOut = new ServletOutputStreamToByteArrayOutputStream(); + try { + when(resp.getOutputStream()).thenReturn(printOut); + servlet.doGet(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(200); + String responseBody = printOut.getByteArrayOutputStream().toString(StandardCharsets.UTF_8); + System.out.println(responseBody); + OdlPolicy[] anonPolicies = null; + try { + anonPolicies = readJson(responseBody, OdlPolicy[].class); + } catch (JsonProcessingException e) { + fail("unable to read anon policies response"); + } + assertEquals(9, anonPolicies.length); + OdlPolicy pApidoc = find(anonPolicies, "/apidoc/**"); + assertNotNull(pApidoc); + assertAllEquals(true, pApidoc); + OdlPolicy pOauth = find(anonPolicies, "/oauth/**"); + assertNotNull(pOauth); + assertAllEquals(true, pOauth); + OdlPolicy pRestconf = find(anonPolicies, "/rests/**"); + assertNotNull(pRestconf); + assertAllEquals(true, pRestconf); + } + + @Test + public void testPoliciesBearer() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getRequestURI()).thenReturn("/oauth/policies"); + String token = createToken("admin", Arrays.asList("admin", "provision")).getToken(); + when(req.getHeader("Authorization")).thenReturn(String.format("Bearer %s", token)); + HttpServletResponse resp = mock(HttpServletResponse.class); + ServletOutputStreamToByteArrayOutputStream printOut = new ServletOutputStreamToByteArrayOutputStream(); + try { + when(resp.getOutputStream()).thenReturn(printOut); + servlet.doGet(req, resp); + } catch (ServletException | IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(200); + String responseBody = printOut.getByteArrayOutputStream().toString(StandardCharsets.UTF_8); + System.out.println(responseBody); + OdlPolicy[] anonPolicies = null; + try { + anonPolicies = readJson(responseBody, OdlPolicy[].class); + } catch (JsonProcessingException e) { + fail("unable to read anon policies response"); + } + assertEquals(9, anonPolicies.length); + OdlPolicy pApidoc = find(anonPolicies, "/apidoc/**"); + assertNotNull(pApidoc); + assertAllEquals(false, pApidoc); + OdlPolicy pOauth = find(anonPolicies, "/oauth/**"); + assertNotNull(pOauth); + assertAllEquals(true, pOauth); + OdlPolicy pRestconf = find(anonPolicies, "/rests/**"); + assertNotNull(pRestconf); + assertAllEquals(true, pRestconf); + } + + private static BearerToken createToken(String username, List<String> roles) { + UserTokenPayload data = new UserTokenPayload(); + data.setPreferredUsername(username); + data.setFamilyName(""); + data.setGivenName(username); + data.setExp(tokenCreator.getDefaultExp()); + data.setRoles(roles); + return tokenCreator.createNewJWT(data); + } + + private static void assertAllEquals(boolean b, OdlPolicy p) { + assertEquals(b, p.getMethods().isGet()); + assertEquals(b, p.getMethods().isPost()); + assertEquals(b, p.getMethods().isPut()); + assertEquals(b, p.getMethods().isDelete()); + assertEquals(b, p.getMethods().isPatch()); + } + + private static OdlPolicy find(OdlPolicy[] policies, String path) { + for (OdlPolicy p : policies) { + if (path.equals(p.getPath())) { + return p; + } + } + return null; + } + + private static <T> T readJson(String data, Class<T> clazz) throws JsonMappingException, JsonProcessingException { + CustomObjectMapper mapper = new CustomObjectMapper(); + return mapper.readValue(data, clazz); + } + + private static <T> T readJson(File file, Class<T> clazz) throws IOException { + OdlJsonMapper mapper = new OdlJsonMapper(); + return mapper.readValue(file, clazz); + } + + private static class TestServlet extends AuthHttpServlet { + + private static final long serialVersionUID = 1L; + + public TestServlet() throws IOException { + super(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + super.doGet(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + super.doPost(req, resp); + } + } +} 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 index 2b88141ae..d07950de7 100644 --- 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 @@ -27,10 +27,19 @@ import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config; public class TestConfig { + public static String TEST_CONFIG_FILENAME = "src/test/resources/test.config.json"; + public static String TEST_OOMCONFIG_FILENAME = "src/test/resources/oom.test.config.json"; @Test public void test() throws IOException { - Config config = Config.load("src/test/resources/test.config.json"); + Config config = Config.load(TEST_CONFIG_FILENAME); System.out.println("config="+config); } + @Test + public void testOom() throws IOException { + + Config config = Config.load(TEST_OOMCONFIG_FILENAME); + 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 index 236c0ffac..65ef2cbd6 100644 --- 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 @@ -28,6 +28,7 @@ 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.common.http.BaseHTTPResponse; 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; @@ -50,7 +51,8 @@ public class TestDeserializer { + "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; + BaseHTTPResponse res = new BaseHTTPResponse(200, response); + OAuthResponseData data = new MappedBaseHttpResponse<>(res,OAuthResponseData.class).body; assertEquals(token,data.getAccess_token()); } diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestGitlabAuthService.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestGitlabAuthService.java new file mode 100644 index 000000000..fb938000e --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestGitlabAuthService.java @@ -0,0 +1,195 @@ +/* + * ============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.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.GitlabProviderService; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator; + +public class TestGitlabAuthService { + + private static HttpServer server; + private static ExecutorService httpThreadPool; + private static GitlabProviderServiceToTest oauthService; + private static final int PORT = randomPort(50000, 55000); + private static final String GITURL = String.format("http://127.0.0.1:%d", PORT); + private static final String OAUTH_SECRET = "oauthsecret"; + private static final String TOKENCREATOR_SECRET = "secret"; + private static final String REDIRECT_URI = "/odlux/token?"; + + @BeforeClass + public static void init() { + + TokenCreator tokenCreator = TokenCreator.getInstance(TOKENCREATOR_SECRET, "issuer"); + OAuthProviderConfig config = + new OAuthProviderConfig("git", GITURL, "odlux.app", OAUTH_SECRET, "openid", "gitlab test"); + oauthService = new GitlabProviderServiceToTest(config, REDIRECT_URI, tokenCreator); + try { + initGitlabTestWebserver(PORT, "/"); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @AfterClass + public static void close() { + stopTestWebserver(); + } + + @Test + public void test() { + HttpServletRequest req; + HttpServletResponse resp = null; + String host = "http://localhost:8412"; + final String state = "stateabc"; + try { + req = mock(HttpServletRequest.class); + resp = mock(HttpServletResponse.class); + when(req.getParameter("code")).thenReturn("abcdefg"); + when(req.getParameter("state")).thenReturn(state); + oauthService.addState(state); + oauthService.handleRedirect(req, resp, host); + } catch (IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(302); + //verify(resp).setHeader("Location",any(String.class)); + } + + public void test2() { + oauthService.sendLoginRedirectResponse(null, null); + } + + public static class GitlabProviderServiceToTest extends GitlabProviderService { + + public GitlabProviderServiceToTest(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) { + super(config, redirectUri, tokenCreator); + } + + public void addState(String state) { + this.randomIds.add(state); + } + } + + private static int randomPort(int min, int max) { + Random random = new Random(); + return random.nextInt(max + 1 - min) + min; + } + + public static void initGitlabTestWebserver(int port, String baseUri) throws IOException { + server = HttpServer.create(new InetSocketAddress("127.0.0.1", port), 0); + httpThreadPool = Executors.newFixedThreadPool(5); + server.setExecutor(httpThreadPool); + server.createContext(baseUri, new MyHandler()); + //server.createContext("/", new MyRootHandler()); + server.setExecutor(null); // creates a default executor + server.start(); + System.out.println("http server started"); + } + + public static void stopTestWebserver() { + if (server != null) { + server.stop(0); + httpThreadPool.shutdownNow(); + System.out.println("http server stopped"); + } + } + + private static String loadResourceFileContent(String filename) { + try { + return Files.readString(new File(filename).toPath()); + } catch (IOException e) { + fail(e.getMessage()); + } + return null; + } + public static class MyHandler implements HttpHandler { + private static final String GITLAB_TOKEN_ENDPOINT = "/oauth/token"; + private static final String GITLAB_USER_ENDPOINT = "/api/v4/user"; + private static final String GITLAB_GROUP_ENDPOINT = "/api/v4/groups?min_access_level=10"; + private static final String GITLAB_TOKEN_RESPONSE = loadResourceFileContent("src/test/resources/oauth/gitlab-token-response.json"); + private static final String GITLAB_USER_RESPONSE =loadResourceFileContent("src/test/resources/oauth/gitlab-user-response.json"); + private static final String GITLAB_GROUP_RESPONSE =loadResourceFileContent("src/test/resources/oauth/gitlab-groups-response.json"); + + @Override + public void handle(HttpExchange t) throws IOException { + final String method = t.getRequestMethod(); + final String uri = t.getRequestURI().toString(); + System.out.println(String.format("req received: %s %s", method, t.getRequestURI())); + OutputStream os = null; + String response = ""; + try { + if (method.equals("GET")) { + if(uri.equals(GITLAB_USER_ENDPOINT)) { + t.sendResponseHeaders(200, GITLAB_USER_RESPONSE.length()); + os = t.getResponseBody(); + os.write(GITLAB_USER_RESPONSE.getBytes()); + } + else if(uri.equals(GITLAB_GROUP_ENDPOINT)) { + t.sendResponseHeaders(200, GITLAB_GROUP_RESPONSE.length()); + os = t.getResponseBody(); + os.write(GITLAB_GROUP_RESPONSE.getBytes()); + } + } else if (method.equals("POST")) { + if(uri.equals(GITLAB_TOKEN_ENDPOINT)){ + t.sendResponseHeaders(200, GITLAB_TOKEN_RESPONSE.length()); + os = t.getResponseBody(); + os.write(GITLAB_TOKEN_RESPONSE.getBytes()); + } + else { + t.sendResponseHeaders(404, 0); + } + } else { + t.sendResponseHeaders(404, 0); + } + System.out.println("req handled successful"); + + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + if (os != null) { + os.close(); + } + } + } + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestKeycloakAuthService.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestKeycloakAuthService.java new file mode 100644 index 000000000..945ad7ff5 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestKeycloakAuthService.java @@ -0,0 +1,174 @@ +/* + * ============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.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.KeycloakProviderService; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator; + +public class TestKeycloakAuthService { + + private static HttpServer server; + private static ExecutorService httpThreadPool; + private static KeycloakProviderServiceToTest oauthService; + private static final int PORT = randomPort(50000, 55000); + private static final String KEYCLOAKURL = String.format("http://127.0.0.1:%d", PORT); + private static final String OAUTH_SECRET = "oauthsecret"; + private static final String TOKENCREATOR_SECRET = "secret"; + private static final String REDIRECT_URI = "/odlux/token?"; + + @BeforeClass + public static void init() { + + TokenCreator tokenCreator = TokenCreator.getInstance(TOKENCREATOR_SECRET, "issuer"); + OAuthProviderConfig config = + new OAuthProviderConfig("kc", KEYCLOAKURL, "odlux.app", OAUTH_SECRET, "openid", "keycloak test"); + oauthService = new KeycloakProviderServiceToTest(config, REDIRECT_URI, tokenCreator); + try { + initKeycloakTestWebserver(PORT, "/"); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @AfterClass + public static void close() { + stopTestWebserver(); + } + + @Test + public void test() { + HttpServletRequest req; + HttpServletResponse resp = null; + String host = "http://localhost:8412"; + final String state = "stateabc"; + try { + req = mock(HttpServletRequest.class); + resp = mock(HttpServletResponse.class); + when(req.getParameter("code")).thenReturn("abcdefg"); + when(req.getParameter("state")).thenReturn(state); + oauthService.handleRedirect(req, resp, host); + } catch (IOException e) { + fail(e.getMessage()); + } + verify(resp).setStatus(302); + //verify(resp).setHeader("Location",any(String.class)); + } + + public void test2() { + oauthService.sendLoginRedirectResponse(null, null); + } + + public static class KeycloakProviderServiceToTest extends KeycloakProviderService { + + public KeycloakProviderServiceToTest(OAuthProviderConfig config, String redirectUri, TokenCreator tokenCreator) { + super(config, redirectUri, tokenCreator); + } + } + + private static int randomPort(int min, int max) { + Random random = new Random(); + return random.nextInt(max + 1 - min) + min; + } + + public static void initKeycloakTestWebserver(int port, String baseUri) throws IOException { + server = HttpServer.create(new InetSocketAddress("127.0.0.1", port), 0); + httpThreadPool = Executors.newFixedThreadPool(5); + server.setExecutor(httpThreadPool); + server.createContext(baseUri, new MyHandler()); + //server.createContext("/", new MyRootHandler()); + server.setExecutor(null); // creates a default executor + server.start(); + System.out.println("http server started"); + } + + public static void stopTestWebserver() { + if (server != null) { + server.stop(0); + httpThreadPool.shutdownNow(); + System.out.println("http server stopped"); + } + } + + private static String loadResourceFileContent(String filename) { + try { + return Files.readString(new File(filename).toPath()); + } catch (IOException e) { + fail(e.getMessage()); + } + return null; + } + public static class MyHandler implements HttpHandler { + private static final String KEYCLOAK_TOKEN_ENDPOINT = "/auth/realms/onap/protocol/openid-connect/token"; + private static final String KEYCLOAK_TOKEN_RESPONSE = loadResourceFileContent("src/test/resources/oauth/keycloak-token-response.json"); + + @Override + public void handle(HttpExchange t) throws IOException { + final String method = t.getRequestMethod(); + final String uri = t.getRequestURI().toString(); + System.out.println(String.format("req received: %s %s", method, t.getRequestURI())); + OutputStream os = null; + try { + if (method.equals("POST")) { + if(uri.equals(KEYCLOAK_TOKEN_ENDPOINT)){ + t.sendResponseHeaders(200, KEYCLOAK_TOKEN_RESPONSE.length()); + os = t.getResponseBody(); + os.write(KEYCLOAK_TOKEN_RESPONSE.getBytes()); + } + else { + t.sendResponseHeaders(404, 0); + } + } else { + t.sendResponseHeaders(404, 0); + } + System.out.println("req handled successful"); + + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + if (os != null) { + os.close(); + } + } + } + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestPolicy.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestPolicy.java new file mode 100644 index 000000000..31d72944c --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestPolicy.java @@ -0,0 +1,56 @@ +/* + * ============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.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OdlPolicy; + +public class TestPolicy { + + private static final String PATH_1 = "/p1/**"; + + @Test + public void testPolicyAllowAll() { + OdlPolicy p = OdlPolicy.allowAll(PATH_1); + assertTrue(p.getMethods().isGet()); + assertTrue(p.getMethods().isPost()); + assertTrue(p.getMethods().isPut()); + assertTrue(p.getMethods().isDelete()); + assertTrue(p.getMethods().isPatch()); + assertEquals(PATH_1,p.getPath()); + } + + @Test + public void testPolicyDenyAll() { + OdlPolicy p = OdlPolicy.denyAll(PATH_1); + assertFalse(p.getMethods().isGet()); + assertFalse(p.getMethods().isPost()); + assertFalse(p.getMethods().isPut()); + assertFalse(p.getMethods().isDelete()); + assertFalse(p.getMethods().isPatch()); + assertEquals(PATH_1,p.getPath()); + } + +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestRealm.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestRealm.java new file mode 100644 index 000000000..fe9dae31d --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/TestRealm.java @@ -0,0 +1,178 @@ +/* + * ============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.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.auth0.jwt.interfaces.DecodedJWT; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.subject.PrincipalCollection; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.oauthprovider.OAuth2Realm; +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.providers.TokenCreator; +import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal; +import org.opendaylight.aaa.shiro.filters.backport.BearerToken; +import org.opendaylight.aaa.shiro.tokenauthrealm.auth.AuthenticationManager; +import org.opendaylight.aaa.shiro.tokenauthrealm.auth.TokenAuthenticators; +import org.opendaylight.aaa.shiro.web.env.ThreadLocals; + +public class TestRealm { + + private static OAuth2RealmToTest realm; + private static TokenCreator tokenCreator; + + @BeforeClass + public static void init() { + ThreadLocals.AUTH_SETVICE_TL.set(new AuthenticationManager()); + ThreadLocals.TOKEN_AUTHENICATORS_TL.set(new TokenAuthenticators()); + try { + Config config = Config.getInstance(TestConfig.TEST_CONFIG_FILENAME); + tokenCreator = TokenCreator.getInstance(config); + realm = new OAuth2RealmToTest(); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + + @Test + public void testTokenSupport() { + assertTrue(realm.supports(new UsernamePasswordToken())); + assertTrue(realm.supports(new BearerToken(""))); + } + + + @Test + public void testAuthorizationInfo() { + //bearer token use case + PrincipalCollection c = mock(PrincipalCollection.class); + final List<String> roles = Arrays.asList("admin", "provision"); + UserTokenPayload userData = createUserData("", roles); + + DecodedJWT decodedJwt = tokenCreator.verify(tokenCreator.createNewJWT(userData).getToken()); + when(c.getPrimaryPrincipal()).thenReturn(decodedJwt); + + AuthorizationInfo ai = realm.doGetAuthorizationInfo(c); + for (String role : roles) { + assertTrue(ai.getRoles().contains(role)); + } + assertEquals(roles.size(), ai.getRoles().size()); + //odl token use case + ODLPrincipal principal = mock(ODLPrincipal.class); + when(principal.getRoles()).thenReturn(new HashSet<String>(roles)); + PrincipalCollection c2 = mock(PrincipalCollection.class); + when(c2.getPrimaryPrincipal()).thenReturn(principal); + ai = realm.doGetAuthorizationInfo(c2); + for (String role : roles) { + assertTrue(ai.getRoles().contains(role)); + } + assertEquals(roles.size(), ai.getRoles().size()); + + } + + @Test + public void testAssertCredentialsMatch() { + //bearer token use case + UserTokenPayload userData = createUserData("", Arrays.asList("admin", "provision")); + AuthenticationToken atoken = new BearerToken(tokenCreator.createNewJWT(userData).getToken()); + AuthenticationInfo ai = null; + try { + realm.assertCredentialsMatch(atoken, ai); + } catch (AuthenticationException e) { + fail(e.getMessage()); + } + //odl token use case + atoken = new UsernamePasswordToken("admin", "admin"); + try { + realm.assertCredentialsMatch(atoken, ai); + } catch (AuthenticationException e) { + fail(e.getMessage()); + } + } + + @Test + public void testAuthenticationInfo() { + //bearer token use case + UserTokenPayload userData = createUserData("", Arrays.asList("admin", "provision")); + AuthenticationToken atoken = new BearerToken(tokenCreator.createNewJWT(userData).getToken()); + AuthenticationInfo ai = null; + try { + ai = realm.doGetAuthenticationInfo(atoken); + } catch (AuthenticationException e) { + fail(e.getMessage()); + } + //odl token use case + atoken = new UsernamePasswordToken("admin", "admin"); + try { + ai = realm.doGetAuthenticationInfo(atoken); + } catch (AuthenticationException e) { + fail(e.getMessage()); + } + } + + private static UserTokenPayload createUserData(String username, List<String> roles) { + UserTokenPayload userData = new UserTokenPayload(); + userData.setExp(tokenCreator.getDefaultExp()); + userData.setFamilyName(""); + userData.setGivenName(""); + userData.setPreferredUsername(username); + userData.setRoles(roles); + return userData; + } + + public static class OAuth2RealmToTest extends OAuth2Realm { + + public OAuth2RealmToTest() throws IOException { + super(); + } + + @Override + public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg) { + return super.doGetAuthorizationInfo(arg); + } + + @Override + public void assertCredentialsMatch(AuthenticationToken atoken, AuthenticationInfo ai) + throws AuthenticationException { + super.assertCredentialsMatch(atoken, ai); + } + + @Override + public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + return super.doGetAuthenticationInfo(token); + } + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlJsonMapper.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlJsonMapper.java new file mode 100644 index 000000000..0399655ed --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlJsonMapper.java @@ -0,0 +1,66 @@ +/* + * ============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.helper; + +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; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import org.onap.ccsdk.features.sdnr.wt.dataprovider.yangtools.mapperextensions.YangToolsBuilderAnnotationIntrospector; +import org.onap.ccsdk.features.sdnr.wt.dataprovider.yangtools.mapperextensions.YangToolsModule; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions; +import org.opendaylight.yangtools.concepts.Builder; + +public class OdlJsonMapper extends ObjectMapper { + + private static final long serialVersionUID = 1L; + + + public OdlJsonMapper() { + this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.setSerializationInclusion(Include.NON_NULL); + this.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + this.enable(MapperFeature.USE_GETTERS_AS_SETTERS); + YangToolsBuilderAnnotationIntrospector introspector = new YangToolsBuilderAnnotationIntrospector(); + //introspector.addDeserializer(Main.class, ShiroMainBuilder.class.getName()); + introspector.addDeserializer(Permissions.class,PermissionsBuilder.class.getName()); + this.setAnnotationIntrospector(introspector); + this.registerModule(new YangToolsModule()); + } + + public static class PermissionsBuilder implements Builder<Permissions> { + private Permissions _value; + + public PermissionsBuilder() { + + } + public PermissionsBuilder(Permissions value) { + this._value = value; + } + + @Override + public Permissions build() { + return this._value; + } + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlXmlMapper.java b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlXmlMapper.java new file mode 100644 index 000000000..cc029bd05 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/java/org/onap/ccsdk/features/sdnr/wt/oauthprovider/test/helper/OdlXmlMapper.java @@ -0,0 +1,46 @@ +/* + * ============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.helper; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.onap.ccsdk.features.sdnr.wt.dataprovider.yangtools.mapperextensions.YangToolsBuilderAnnotationIntrospector; + +public class OdlXmlMapper extends XmlMapper{ + + private static final long serialVersionUID = 1L; + + + public OdlXmlMapper() { + this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.setSerializationInclusion(Include.NON_NULL); + this.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + this.enable(MapperFeature.USE_GETTERS_AS_SETTERS); + YangToolsBuilderAnnotationIntrospector introspector = new YangToolsBuilderAnnotationIntrospector(); + //introspector.addDeserializer(Main.class, ShiroMainBuilder.class.getName()); + + this.setAnnotationIntrospector(introspector); + } +} diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/aaa-app-config.test.xml b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/aaa-app-config.test.xml new file mode 100644 index 000000000..017c7439d --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/aaa-app-config.test.xml @@ -0,0 +1,351 @@ +<?xml version="1.0" ?> +<!-- + Copyright (c) 2017 Inocybe Technologies and others. All rights reserved. + + This program and the accompanying materials are made available under the + terms of the Eclipse Public License v1.0 which accompanies this distribution, + and is available at http://www.eclipse.org/legal/epl-v10.html +--> + +<!-- + /////////////////////////////////////////////////////////////////////////////////////// + // clustered-app-config instance responsible for AAA configuration. In the future, // + // this will contain all AAA related configuration. // + /////////////////////////////////////////////////////////////////////////////////////// +--> + +<shiro-configuration xmlns="urn:opendaylight:aaa:app:config"> + + <!-- + /////////////////////////////////////////////////////////////////////////////////// + // shiro-configuration is the model based container that contains all shiro // + // related information used in ODL AAA configuration. It is the sole pain of // + // glass for shiro related configuration, and is how to configure shiro concepts // + // such as: // + // * realms // + // * urls // + // * security manager settings // + // // + // In general, you really shouldn't muck with the settings in this file. The // + // way an operator should configure AAA shiro settings is through one of ODL's // + // northbound interfaces (i.e., RESTCONF or NETCONF). These are just the // + // defaults if no values are specified in MD-SAL. The reason this file is so // + // verbose is for two reasons: // + // 1) to demonstrate payload examples for plausible configuration scenarios // + // 2) to allow bootstrap of the controller (first time start) since otherwise // + // configuration becomes a chicken and the egg problem. // + // // + /////////////////////////////////////////////////////////////////////////////////// + --> + + <!-- + =================================================================================== + = = + = = + = MAIN = + = = + = = + =================================================================================== + --> + + <!-- + =================================================================================== + ============================ ODLJndiLdapRealmAuthNOnly ============================ + =================================================================================== + = = + = Description: A Realm implementation aimed at federating with an external LDAP = + = server for authentication only. For authorization support, refer = + = to ODLJndiLdapRealm. = + =================================================================================== + --> + <!-- Start ldapRealm commented out + <main> + <pair-key>ldapRealm</pair-key> + <pair-value>org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealmAuthNOnly</pair-value> + </main> + <main> + <pair-key>ldapRealm.userDnTemplate</pair-key> + <pair-value>uid={0},ou=People,dc=DOMAIN,dc=TLD</pair-value> + </main> + <main> + <pair-key>ldapRealm.contextFactory.url</pair-key> + <pair-value>ldap://<URL>:389</pair-value> + </main> + <main> + <pair-key>ldapRealm.searchBase</pair-key> + <pair-value>dc=DOMAIN,dc=TLD</pair-value> + </main> + <main> + <pair-key>ldapRealm.groupRolesMap</pair-key> + <pair-value>"person":"admin", "organizationalPerson":"user"</pair-value> + </main> + <main> + <pair-key>ldapRealm.ldapAttributeForComparison</pair-key> + <pair-value>objectClass</pair-value> + </main> + End ldapRealm commented out--> + + <!-- + =================================================================================== + ============================= ODLActiveDirectoryRealm ============================= + =================================================================================== + = = + = Description: A Realm implementation aimed at federating with an external AD = + = IDP server. = + =================================================================================== + --> + <!-- Start adRealm commented out + <main> + <pair-key>adRealm</pair-key> + <pair-value>org.opendaylight.aaa.shiro.realm.ODLActiveDirectoryRealm</pair-value> + </main> + <main> + <pair-key>adRealm.searchBase</pair-key> + <pair-value>"CN=Users,DC=example,DC=com"</pair-value> + </main> + <main> + <pair-key>adRealm.systemUsername</pair-key> + <pair-value>aduser@example.com</pair-value> + </main> + <main> + <pair-key>adRealm.systemPassword</pair-key> + <pair-value>adpassword</pair-value> + </main> + <main> + <pair-key>adRealm.url</pair-key> + <pair-value>ldaps://adserver:636</pair-value> + </main> + <main> + <pair-key>adRealm.groupRolesMap</pair-key> + <pair-value>"CN=sysadmin,CN=Users,DC=example,DC=com":"admin", "CN=unprivileged,CN=Users,DC=example,DC=com":"user"</pair-value> + </main> + End adRealm commented out--> + + <!-- + =================================================================================== + ================================== ODLJdbcRealm =================================== + =================================================================================== + = = + = Description: A Realm implementation aimed at federating with an external JDBC = + = DBMS. = + =================================================================================== + --> + <!-- Start jdbcRealm commented out + <main> + <pair-key>ds</pair-key> + <pair-value>com.mysql.jdbc.Driver</pair-value> + </main> + <main> + <pair-key>ds.serverName</pair-key> + <pair-value>localhost</pair-value> + </main> + <main> + <pair-key>ds.user</pair-key> + <pair-value>user</pair-value> + </main> + <main> + <pair-key>ds.password</pair-key> + <pair-value>password</pair-value> + </main> + <main> + <pair-key>ds.databaseName</pair-key> + <pair-value>db_name</pair-value> + </main> + <main> + <pair-key>jdbcRealm</pair-key> + <pair-value>ODLJdbcRealm</pair-value> + </main> + <main> + <pair-key>jdbcRealm.dataSource</pair-key> + <pair-value>$ds</pair-value> + </main> + <main> + <pair-key>jdbcRealm.authenticationQuery</pair-key> + <pair-value>"SELECT password FROM users WHERE user_name = ?"</pair-value> + </main> + <main> + <pair-key>jdbcRealm.userRolesQuery</pair-key> + <pair-value>"SELECT role_name FROM user_rolesWHERE user_name = ?"</pair-value> + </main> + End jdbcRealm commented out--> + + <!-- + =================================================================================== + ================================= TokenAuthRealm ================================== + =================================================================================== + = = + = Description: A Realm implementation utilizing a per node H2 database store. = + =================================================================================== + --> +<!-- <main> --> +<!-- <pair-key>tokenAuthRealm</pair-key> --> +<!-- <pair-value>org.opendaylight.aaa.shiro.realm.TokenAuthRealm</pair-value> --> +<!-- </main> --> + <main> + <pair-key>tokenAuthRealm</pair-key> + <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.OAuth2Realm</pair-value> + </main> + + <!-- + =================================================================================== + =================================== MdsalRealm ==================================== + =================================================================================== + = = + = Description: A Realm implementation utilizing the aaa.yang model. = + =================================================================================== + --> + <!-- Start mdsalRealm commented out + <main> + <pair-key>mdsalRealm</pair-key> + <pair-value>org.opendaylight.aaa.shiro.realm.MdsalRealm</pair-value> + </main> + End mdsalRealm commented out--> + + <!-- + =================================================================================== + ================================= MoonAuthRealm =================================== + =================================================================================== + = = + = Description: A Realm implementation aimed at federating with OPNFV Moon. = + =================================================================================== + --> + <!-- Start moonAuthRealm commented out + <main> + <pair-key>moonAuthRealm</pair-key> + <pair-value>org.opendaylight.aaa.shiro.realm.MoonRealm</pair-value> + </main> + <main> + <pair-key>moonAuthRealm.moonServerURL</pair-key> + <pair-value>http://<host>:<port></pair-value> + </main> + End moonAuthRealm commented out--> + + <!-- + =================================================================================== + ================================= KeystoneAuthRealm == ============================ + =================================================================================== + = = + = Description: A Realm implementation aimed at federating with an OpenStack = + = Keystone. = + =================================================================================== + --> + <!-- Start keystoneAuthRealm commented out + <main> + <pair-key>keystoneAuthRealm</pair-key> + <pair-value>org.opendaylight.aaa.shiro.realm.KeystoneAuthRealm</pair-value> + </main> + <main> + <pair-key>keystoneAuthRealm.url</pair-key> + <pair-value>https://<host>:<port></pair-value> + </main> + <main> + <pair-key>keystoneAuthRealm.sslVerification</pair-key> + <pair-value>true</pair-value> + </main> + <main> + <pair-key>keystoneAuthRealm.defaultDomain</pair-key> + <pair-value>Default</pair-value> + </main> + --> + + <!-- + Add tokenAuthRealm as the only realm. To enable mdsalRealm, add it to the list to he right of tokenAuthRealm. + --> + <main> + <pair-key>securityManager.realms</pair-key> + <pair-value>$tokenAuthRealm</pair-value> + </main> + <!-- Used to support OAuth2 use case. --> + <main> + <pair-key>authcBasic</pair-key> + <pair-value>org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter</pair-value> + </main> + <main> + <pair-key>anyroles</pair-key> + <pair-value>org.opendaylight.aaa.shiro.filters.AnyRoleHttpAuthenticationFilter</pair-value> + </main> + <main> + <pair-key>authcBearer</pair-key> + <pair-value>org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter2</pair-value> + </main> + + <!-- Start moonAuthRealm commented out + <main> + <pair-key>rest</pair-key> + <pair-value>org.opendaylight.aaa.shiro.filters.MoonOAuthFilter</pair-value> + </main> + End moonAuthRealm commented out--> + + <!-- in order to track AAA challenge attempts --> + <main> + <pair-key>accountingListener</pair-key> + <pair-value>org.opendaylight.aaa.shiro.filters.AuthenticationListener</pair-value> + </main> + <main> + <pair-key>securityManager.authenticator.authenticationListeners</pair-key> + <pair-value>$accountingListener</pair-value> + </main> + + <!-- Model based authorization scheme supporting RBAC for REST endpoints --> + <main> + <pair-key>dynamicAuthorization</pair-key> + <pair-value>org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter</pair-value> + </main> +<!-- <main> --> +<!-- <pair-key>securityManager.sessionManager.sessionIdCookieEnabled</pair-key> --> +<!-- <pair-value>false</pair-value> --> +<!-- </main> --> + + <!-- + =================================================================================== + = = + = = + = URLS = + = = + = = + =================================================================================== + --> + <!-- Start moonAuthRealm commented out + <urls> + <pair-key>/token</pair-key> + <pair-value>rest</pair-value> + </urls> + End moonAuthRealm commented out--> + <urls> + <pair-key>/**/operations/cluster-admin**</pair-key> + <pair-value>dynamicAuthorization</pair-value> + </urls> + <urls> + <pair-key>/**/v1/**</pair-key> + <pair-value>authcBearer, roles[admin]</pair-value> + </urls> + <urls> + <pair-key>/**/config/aaa*/**</pair-key> + <pair-value>authcBearer, roles[admin]</pair-value> + </urls> + <urls> + <pair-key>/oauth/**</pair-key> + <pair-value>anon</pair-value> + </urls> + <urls> + <pair-key>/odlux/**</pair-key> + <pair-value>anon</pair-value> + </urls> + <urls> + <pair-key>/apidoc/**</pair-key> + <pair-value>authcBasic, roles[admin]</pair-value> + </urls> + <urls> + <pair-key>/test123/**</pair-key> + <pair-value>authcBasic</pair-value> + </urls> + <urls> + <pair-key>/rests/**</pair-key> + <pair-value>authcBearer, anyroles["admin,provision"]</pair-value> + </urls> + <urls> + <pair-key>/**</pair-key> + <pair-value>authcBearer, anyroles["admin,provision"]</pair-value> + </urls> +</shiro-configuration> + diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/mdsalDynAuthData.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/mdsalDynAuthData.json new file mode 100644 index 000000000..a1627682b --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/mdsalDynAuthData.json @@ -0,0 +1,694 @@ +{ + "policies": [ + { + "resource": "/restconf/**", + "index": 0, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + } + ] + }, + { + "resource": "/auth/v1/**", + "index": 1, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + } + ] + }, + { + "resource": "/config/aaa*/**", + "index": 2, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + } + ] + }, + { + "resource": "/jolokia/**", + "index": 3, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + }, + { + "role": "raftstate", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250004/**", + "index": 4, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "huawei", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250005/**", + "index": 5, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "huawei", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250006/**", + "index": 6, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "siae", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250007/**", + "index": 7, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "siae", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250008/**", + "index": 8, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "siae", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250009/**", + "index": 9, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "siae", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250010/**", + "index": 10, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "ericsson", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250011/**", + "index": 11, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "ericsson", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513559991A/**", + "index": 14, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "ericsson", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513559991B/**", + "index": 15, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "ericsson", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250012/**", + "index": 12, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "zte", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/node=513250013/**", + "index": 13, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "zte", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + }, + { + "resource": "/rests/data/network-topology:network-topology/topology=topology-netconf/**", + "index": 16, + "permissions": [ + { + "role": "admin", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "provisioner", + "actions": [ + "put", + "get", + "patch", + "delete", + "post" + ] + }, + { + "role": "planner", + "actions": [ + "get" + ] + }, + { + "role": "monitor", + "actions": [ + "get" + ] + } + ] + } + ] + } diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-groups-response.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-groups-response.json new file mode 100644 index 000000000..85fc37cc8 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-groups-response.json @@ -0,0 +1,112 @@ +[ + { + "id": 51, + "web_url": "https://my-git-server.com/groups/group1", + "name": "Group1", + "path": "group1", + "description": "", + "visibility": "private", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "maintainer", + "emails_disabled": null, + "mentions_disabled": null, + "lfs_enabled": true, + "avatar_url": null, + "request_access_enabled": true, + "full_name": "Group1", + "full_path": "group1", + "parent_id": null + }, + { + "id": 69, + "web_url": "https://my-git-server.com/groups/group2", + "name": "Group2", + "path": "group2", + "description": "", + "visibility": "private", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, + "mentions_disabled": null, + "lfs_enabled": true, + "avatar_url": null, + "request_access_enabled": true, + "full_name": "Group2", + "full_path": "group2", + "parent_id": null + }, + { + "id": 24, + "web_url": "https://my-git-server.com/groups/group3", + "name": "group3", + "path": "group3", + "description": "", + "visibility": "private", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, + "mentions_disabled": null, + "lfs_enabled": true, + "avatar_url": null, + "request_access_enabled": false, + "full_name": "group3", + "full_path": "group3", + "parent_id": null + }, + { + "id": 22, + "web_url": "https://my-git-server.com/groups/group4", + "name": "group4", + "path": "group4", + "description": "custom desc", + "visibility": "private", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, + "mentions_disabled": null, + "lfs_enabled": true, + "avatar_url": "https://my-git-server.com/uploads/-/system/group/avatar/22/Factory_1b.svg.png", + "request_access_enabled": true, + "full_name": "group4", + "full_path": "group4", + "parent_id": null + }, + { + "id": 5, + "web_url": "https://my-git-server.com/groups/group5", + "name": "group5", + "path": "group5", + "description": "my group 5", + "visibility": "private", + "share_with_group_lock": false, + "require_two_factor_authentication": false, + "two_factor_grace_period": 48, + "project_creation_level": "developer", + "auto_devops_enabled": null, + "subgroup_creation_level": "owner", + "emails_disabled": null, + "mentions_disabled": null, + "lfs_enabled": true, + "avatar_url": "https://my-git-server.com/uploads/-/system/group/avatar/5/mylogo.png", + "request_access_enabled": true, + "full_name": "group5", + "full_path": "group5", + "parent_id": null + } +]
\ No newline at end of file diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-token-response.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-token-response.json new file mode 100644 index 000000000..0a6bd7231 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-token-response.json @@ -0,0 +1,7 @@ +{ + "access_token":"asfhoipmspaodm-asndfoiasnf-aisjdaisjj", + "expires_in":12345, + "refresh_expires_in":123456, + "refresh_token":"asdsadasd", + "token_type":"bearer" +}
\ No newline at end of file diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-user-response.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-user-response.json new file mode 100644 index 000000000..b08332b41 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/gitlab-user-response.json @@ -0,0 +1,32 @@ +{ + "id": 21, + "name": "me", + "username": "my-username", + "state": "active", + "avatar_url": "https://my-git-server.com/uploads/-/system/user/avatar/21/avatar.png", + "web_url": "https://my-git-server.com/my-username", + "created_at": "2017-05-15T14:49:38.396Z", + "bio": "", + "location": "", + "public_email": "", + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "organization": "", + "last_sign_in_at": "2021-02-12T12:56:34.094Z", + "confirmed_at": "2018-01-18T09:49:08.463Z", + "last_activity_on": "2021-02-15", + "email": "me@my-server.com", + "theme_id": null, + "color_scheme_id": 1, + "projects_limit": 0, + "current_sign_in_at": "2021-02-15T03:17:12.140Z", + "identities": [], + "can_create_group": false, + "can_create_project": false, + "two_factor_enabled": false, + "external": false, + "private_profile": false, + "is_admin": true +}
\ No newline at end of file diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/keycloak-token-response.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/keycloak-token-response.json new file mode 100644 index 000000000..c62ed9458 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oauth/keycloak-token-response.json @@ -0,0 +1,11 @@ +{ + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkbWFSWXRkaHFkVXFDV2lmRWdNRHFBcWVBcU8tMnFoTDBjdnByelRGdWRRIn0.eyJleHAiOjE2MTExMzU5MjEsImlhdCI6MTYxMTEzNDEyMSwiYXV0aF90aW1lIjoxNjExMTM0MDkxLCJqdGkiOiIzYzFlZmMzZi1lMjFiLTQ3MzktYTY1YS1jNjY1M2ZhOGRjNTQiLCJpc3MiOiJodHRwOi8vMTAuMjAuMTEuMTYwOjgwODAvYXV0aC9yZWFsbXMvb25hcCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI0NDZhMjRiYy1kOGEwLTQzZGQtYWZhNS1lNTZlZWQ3NWRlYjgiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvZGx1eC5hcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTI5YjRhNjMtNzBhMS00MjFmLWEzM2YtOWFjZDkyZTIzM2ZmIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJwcm92aXNpb24iLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ikx1a2UgU2t5d2Fsa2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVrZS5za3l3YWxrZXIiLCJnaXZlbl9uYW1lIjoiTHVrZSIsImZhbWlseV9uYW1lIjoiU2t5d2Fsa2VyIiwiZW1haWwiOiJsdWtlLnNreXdhbGtlckBzZG5yLm9uYXAub3JnIn0.tn2NrEGYLRq1u0DkqxD2iDM72hFrDBPGA_q23S-htiRH113yt14a0CzJxU9El0YDobbzog9xm0ELbx6W4jYsGguMABqIi4W5wtTqfbaCh7gmF208CqNpwzA7nG2palMLbBPpmGXiagUm4qLWQxrBP_VOaeW_kK0VHLaiTRJ-4vHuOXSNPYEDQZNCI2QCJQS_dn83K_JI4ecBHl8UeHFLB65BqmocpDHUvf2h835xuNFFQpXJWMcPM_j_FmFQeOSUDM4HmqgdVU9_b4APnDEVFiUezQdoEOfEYNsNlhCoXlaEEn2tCZfEkZ7k72DlhqJMQzomdaGKPk2g8XhKJNwMJg", + "expires_in": 1800, + "refresh_expires_in": 1800, + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhOGUzMDUwZS0wZmQxLTRjYjQtYjRiZS1jMDVlOGY4OGJhZGUifQ.eyJleHAiOjE2MTExMzU5MjEsImlhdCI6MTYxMTEzNDEyMSwianRpIjoiZmZiYWE3NDktZGVkNi00ZWMzLWI4MjYtYTI4NWY0ODY1ZGI0IiwiaXNzIjoiaHR0cDovLzEwLjIwLjExLjE2MDo4MDgwL2F1dGgvcmVhbG1zL29uYXAiLCJhdWQiOiJodHRwOi8vMTAuMjAuMTEuMTYwOjgwODAvYXV0aC9yZWFsbXMvb25hcCIsInN1YiI6IjQ0NmEyNGJjLWQ4YTAtNDNkZC1hZmE1LWU1NmVlZDc1ZGViOCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvZGx1eC5hcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTI5YjRhNjMtNzBhMS00MjFmLWEzM2YtOWFjZDkyZTIzM2ZmIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCJ9.mt9VHtiBZycHcEuVCOZVjjtyoOGYNaDVvtcA1NPScIQ", + "token_type": "bearer", + "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkbWFSWXRkaHFkVXFDV2lmRWdNRHFBcWVBcU8tMnFoTDBjdnByelRGdWRRIn0.eyJleHAiOjE2MTExMzU5MjEsImlhdCI6MTYxMTEzNDEyMSwiYXV0aF90aW1lIjoxNjExMTM0MDkxLCJqdGkiOiJjZjUzZTc0ZC1kYjZiLTQ4YTUtODkyOS1jYzU3YjY3YjAxN2QiLCJpc3MiOiJodHRwOi8vMTAuMjAuMTEuMTYwOjgwODAvYXV0aC9yZWFsbXMvb25hcCIsImF1ZCI6Im9kbHV4LmFwcCIsInN1YiI6IjQ0NmEyNGJjLWQ4YTAtNDNkZC1hZmE1LWU1NmVlZDc1ZGViOCIsInR5cCI6IklEIiwiYXpwIjoib2RsdXguYXBwIiwic2Vzc2lvbl9zdGF0ZSI6IjEyOWI0YTYzLTcwYTEtNDIxZi1hMzNmLTlhY2Q5MmUyMzNmZiIsImF0X2hhc2giOiJSUXdDclpkQmFKV0VFdmxsRVNxRjV3IiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ikx1a2UgU2t5d2Fsa2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVrZS5za3l3YWxrZXIiLCJnaXZlbl9uYW1lIjoiTHVrZSIsImZhbWlseV9uYW1lIjoiU2t5d2Fsa2VyIiwiZW1haWwiOiJsdWtlLnNreXdhbGtlckBzZG5yLm9uYXAub3JnIn0.rueTNrnvRa4PMo7NS8l4xxRhhNiGzXLmtcUeyWnj3AjFaUoNKuS9l85K3KjRT3zjq494YsepIGuK33I20rvFwDLclcJNHuumAgBnR5dRBi5fLhm7x8YkebhdTHPiYL4hfygpZ7APN1PtcDZnb-uEjjT-RAtjnfk3r-oP6CtqWzI5MjOPnf5HaEwWpkuTjmJf3kyyf_pdhhVkgTwuC-kD8iMjyRIzuZJxVwWVA3S43eL0R7MaIDlpJrOp9EBRfMlObAypc1bLtKwopT0sBla1CM9GmUU2ZYbQb79-hey0rd7CWx1uBkZUxt5myiExBm3pI46boXLP7dzjzxHUKg0m-A", + "not-before-policy": 1611134054, + "session_state": "129b4a63-70a1-421f-a33f-9acd92e233ff", + "scope": "openid profile email" +}
\ No newline at end of file diff --git a/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oom.test.config.json b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oom.test.config.json new file mode 100644 index 000000000..4e5707fa1 --- /dev/null +++ b/sdnr/wt/oauth-provider/provider-jar/src/test/resources/oom.test.config.json @@ -0,0 +1,21 @@ +{ + "tokenSecret": "secret", + "tokenIssuer": "ONAP SDNC", + "publicUrl": "https://sdnc.onap:30205", + "redirectUri": "null", + "supportOdlUsers": "true", + "providers": [ + { + "id": "htgit", + "type": "GITLAB", + "url": "https://git-.com", + "clientId": "f52440b7dcd4bb75", + "secret": "9bd45916f52440b7dcd4bb75", + "scope": "api+openid+read_user+profile", + "title": " Gitlab", + "roleMapping": { + "mygitlabgroup": "admin" + } + } + ] +}
\ No newline at end of file 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 index 88137fdad..157ddb71a 100644 --- 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 @@ -1,14 +1,14 @@ { - "tokenSecret": "${OAUTH_TOKEN_SECRET}", - "tokenIssuer": "${OAUTH_TOKEN_ISSUER}", - "host": "", - "redirectUri": "${OAUTH_ODLUX_REDIRECT_URI}", - "supportOdlUsers": "${OAUTH_SUPPORT_ODLUSERS}", + "tokenSecret": "secret2134", + "tokenIssuer": "ONAP SDNC", + "publicUrl": "http://nasp.diasf.de", + "redirectUri": "/index.html#redirect=", + "supportOdlUsers": "true", "providers": [ { "id": "keycloak", "type": "KEYCLOAK", - "host": "http://10.20.11.160:8080", + "url": "http://10.20.11.160:8080", "clientId": "odlux.app", "secret": "5da4ea3d-8cc9-4669-bd7e-3ecb91d120cd", "scope": "openid", diff --git a/sdnr/wt/oauth-provider/provider-osgi/pom.xml b/sdnr/wt/oauth-provider/provider-osgi/pom.xml index 3d17baf96..6ff4de22b 100644 --- a/sdnr/wt/oauth-provider/provider-osgi/pom.xml +++ b/sdnr/wt/oauth-provider/provider-osgi/pom.xml @@ -118,8 +118,13 @@ org.opendaylight.aaa.api.shiro.principal, org.opendaylight.aaa.shiro.realm, org.opendaylight.aaa.shiro.filters.backport, + org.opendaylight.mdsal.binding.api, org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619, org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619.shiro.configuration, + org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214, + org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization, + org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission, + org.opendaylight.yangtools.yang.binding, com.fasterxml.jackson.databind, com.fasterxml.jackson.databind.deser.std, com.fasterxml.jackson.databind.ser.std, diff --git a/sdnr/wt/oauth-provider/provider-osgi/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml b/sdnr/wt/oauth-provider/provider-osgi/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml index f7dd467cd..ae11de631 100644 --- a/sdnr/wt/oauth-provider/provider-osgi/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml +++ b/sdnr/wt/oauth-provider/provider-osgi/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml @@ -30,6 +30,8 @@ <reference id="odlIdentityService" interface="org.opendaylight.aaa.api.IdMService" /> + <reference id="dataBroker" interface="org.opendaylight.mdsal.binding.api.DataBroker" /> + <odl:clustered-app-config binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.aaa.app.config.rev170619.ShiroConfiguration" id="shiroConfiguration" default-config-file-name="aaa-app-config.xml" /> @@ -39,6 +41,7 @@ <property ref="odlAuthenticator" name="odlAuthenticator" /> <property ref="odlIdentityService" name="odlIdentityService" /> <property ref="shiroConfiguration" name="shiroConfiguration" /> + <property ref="dataBroker" name="dataBroker" /> </bean> <service interface="javax.servlet.http.HttpServlet" ref="authServlet"> |