From f7691f1682dcd6ed99a1d05b6a707c557882be71 Mon Sep 17 00:00:00 2001 From: tamim Date: Thu, 15 Feb 2018 13:58:45 +0100 Subject: [PATCH 1/7] Add end point that allows to get or delete all tokens owned by a particular owner. --- .../AccessTokenForOwnerResource.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java new file mode 100644 index 00000000..3ee3884f --- /dev/null +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012 SURFnet bv, The Netherlands + * + * 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. + */ + +package org.surfnet.oaaas.resource.resourceserver; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.surfnet.oaaas.model.AccessToken; +import org.surfnet.oaaas.repository.AccessTokenRepository; + +/** + * JAX-RS Resource for maintaining owns access tokens. + */ +@Named +@Path("/accessTokenForOwner") +@Produces(MediaType.APPLICATION_JSON) +public class AccessTokenForOwnerResource extends AbstractResource { + + private static final Logger LOG = LoggerFactory.getLogger(AccessTokenForOwnerResource.class); + + @Inject + private AccessTokenRepository accessTokenRepository; + + /** + * Get all access token for the provided credentials (== owner). + */ + @GET + public Response getAll(@Context HttpServletRequest request) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAllAccessTokens(request); + return Response.ok(tokens).build(); + } + + /** + * Get all tokens for a user. + */ + @GET + @Path("/{accessTokenOwner}") + public Response getByOwner(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, owner); + return Response.ok(tokens).build(); + } + + + /** + * Delete all existing access tokens for a user. + */ + @DELETE + @Path("/{accessTokenOwner}") + public Response delete(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, owner); + if (tokens == null || tokens.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + LOG.debug("About to delete accessTokens {}", Arrays.toString(tokens.toArray())); + accessTokenRepository.delete(tokens); + return Response.noContent().build(); + } + + private List getAccessTokensForOwner(HttpServletRequest request, String owner) { + List accessTokens; + String userName = getUserId(request); + if (isAdminPrincipal(request) || owner.equals(userName )) { + accessTokens = accessTokenRepository.findByResourceOwnerId(owner); + LOG.debug("About to return all resource servers ({}) for owner {}", accessTokens.size(), owner); + } else { + accessTokens = new ArrayList<>(); + LOG.debug("User {} is neither admin nor owner. Returning empty list", userName); + } + return accessTokens; + } + + private List getAllAccessTokens(HttpServletRequest request) { + List accessTokens; + if (isAdminPrincipal(request)) { + accessTokens = addAll(accessTokenRepository.findAll().iterator()); + LOG.debug("About to return all resource servers ({}) for adminPrincipal", accessTokens.size()); + } else { + String owner = getUserId(request); + accessTokens = accessTokenRepository.findByResourceOwnerId(owner); + LOG.debug("About to return all resource servers ({}) for owner {}", accessTokens.size(), owner); + } + return accessTokens; + } + + +} From 43bd183376f64307fcc733635e17174d26775bde Mon Sep 17 00:00:00 2001 From: tamim Date: Mon, 23 Apr 2018 10:45:12 +0200 Subject: [PATCH 2/7] Support encrypted id's --- apis-authorization-server/pom.xml | 2 +- .../AccessTokenForOwnerResource.java | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apis-authorization-server/pom.xml b/apis-authorization-server/pom.xml index 0990930c..a15facb2 100644 --- a/apis-authorization-server/pom.xml +++ b/apis-authorization-server/pom.xml @@ -17,7 +17,7 @@ apis-authorization-server - jar + war API Secure - authorization server diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java index 3ee3884f..7debb50b 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java @@ -16,6 +16,9 @@ package org.surfnet.oaaas.resource.resourceserver; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -78,7 +81,6 @@ public Response getByOwner(@Context HttpServletRequest request, @PathParam("acce return Response.ok(tokens).build(); } - /** * Delete all existing access tokens for a user. */ @@ -98,6 +100,27 @@ public Response delete(@Context HttpServletRequest request, @PathParam("accessTo return Response.noContent().build(); } + @GET + @Path("/{accessTokenOwner}") + public Response getByOwnerEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + return getByOwner(request, decode(owner)); + } + + @DELETE + @Path("/{accessTokenOwner}") + public Response deleteEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + return delete(request, decode(owner)); + } + + private String decode(String owner) { + try { + owner = URLDecoder.decode(owner, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + LOG.error(String.format("Error while decoding '%s'", owner), e); + } + return owner; +} + private List getAccessTokensForOwner(HttpServletRequest request, String owner) { List accessTokens; String userName = getUserId(request); From 9c9066aa8afd266f6a036dedcca3c4aa99172c58 Mon Sep 17 00:00:00 2001 From: tamim Date: Mon, 23 Apr 2018 15:44:01 +0200 Subject: [PATCH 3/7] Adapt path --- apis-authorization-server/pom.xml | 1 - .../resourceserver/AccessTokenForOwnerResource.java | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apis-authorization-server/pom.xml b/apis-authorization-server/pom.xml index a15facb2..d93606a1 100644 --- a/apis-authorization-server/pom.xml +++ b/apis-authorization-server/pom.xml @@ -17,7 +17,6 @@ apis-authorization-server - war API Secure - authorization server diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java index 7debb50b..9cffe094 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java @@ -101,14 +101,14 @@ public Response delete(@Context HttpServletRequest request, @PathParam("accessTo } @GET - @Path("/{accessTokenOwner}") - public Response getByOwnerEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + @Path("/{accessTokenOwnerEncrypted}") + public Response getByOwnerEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwnerEncrypted") String owner) { return getByOwner(request, decode(owner)); } @DELETE - @Path("/{accessTokenOwner}") - public Response deleteEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + @Path("/{accessTokenOwnerEncrypted}") + public Response deleteEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwnerEncrypted") String owner) { return delete(request, decode(owner)); } From da0c3e4c085c7724bd3ac7ebb277152a7cf1d337 Mon Sep 17 00:00:00 2001 From: tamim Date: Mon, 23 Apr 2018 16:10:27 +0200 Subject: [PATCH 4/7] Bugfix --- .../AccessTokenForOwnerResource.java | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java index 9cffe094..e1453ca0 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java @@ -73,43 +73,52 @@ public Response getAll(@Context HttpServletRequest request) { @GET @Path("/{accessTokenOwner}") public Response getByOwner(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { - Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); - if (validateScopeResponse != null) { - return validateScopeResponse; - } - List tokens = getAccessTokensForOwner(request, owner); - return Response.ok(tokens).build(); + return doGetByOwner(request, owner); } - /** +/** * Delete all existing access tokens for a user. */ @DELETE @Path("/{accessTokenOwner}") public Response delete(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { - Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); - if (validateScopeResponse != null) { - return validateScopeResponse; - } - List tokens = getAccessTokensForOwner(request, owner); - if (tokens == null || tokens.isEmpty()) { - return Response.status(Response.Status.NOT_FOUND).build(); - } - LOG.debug("About to delete accessTokens {}", Arrays.toString(tokens.toArray())); - accessTokenRepository.delete(tokens); - return Response.noContent().build(); + return doDelete(request, owner); } @GET @Path("/{accessTokenOwnerEncrypted}") public Response getByOwnerEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwnerEncrypted") String owner) { - return getByOwner(request, decode(owner)); + return doGetByOwner(request, decode(owner)); } @DELETE @Path("/{accessTokenOwnerEncrypted}") public Response deleteEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwnerEncrypted") String owner) { - return delete(request, decode(owner)); + return doDelete(request, decode(owner)); + } + + private Response doGetByOwner(HttpServletRequest request, String owner) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, owner); + return Response.ok(tokens).build(); + } + + private Response doDelete (HttpServletRequest request, String owner ) { + + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, owner); + if (tokens == null || tokens.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + LOG.debug("About to delete accessTokens {}", Arrays.toString(tokens.toArray())); + accessTokenRepository.delete(tokens); + return Response.noContent().build(); } private String decode(String owner) { From 74d11ea0da3ecfd13e438e783742699489c49179 Mon Sep 17 00:00:00 2001 From: tamim Date: Mon, 23 Apr 2018 16:29:57 +0200 Subject: [PATCH 5/7] Bugfix --- .../AccessTokenForOwnerEncryptedResource.java | 139 ++++++++++++++++++ .../AccessTokenForOwnerResource.java | 69 +++------ 2 files changed, 157 insertions(+), 51 deletions(-) create mode 100644 apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerEncryptedResource.java diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerEncryptedResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerEncryptedResource.java new file mode 100644 index 00000000..44cfb7ad --- /dev/null +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerEncryptedResource.java @@ -0,0 +1,139 @@ +/* + * Copyright 2012 SURFnet bv, The Netherlands + * + * 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. + */ + +package org.surfnet.oaaas.resource.resourceserver; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.surfnet.oaaas.model.AccessToken; +import org.surfnet.oaaas.repository.AccessTokenRepository; + +/** + * JAX-RS Resource for maintaining owns access tokens. + */ +@Named +@Path("/accessTokenForOwnerEncrypted") +@Produces(MediaType.APPLICATION_JSON) +public class AccessTokenForOwnerEncryptedResource extends AbstractResource { + + private static final Logger LOG = LoggerFactory.getLogger(AccessTokenForOwnerEncryptedResource.class); + + @Inject + private AccessTokenRepository accessTokenRepository; + + /** + * Get all access token for the provided credentials (== owner). + */ + @GET + public Response getAll(@Context HttpServletRequest request) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAllAccessTokens(request); + return Response.ok(tokens).build(); + } + + /** + * Get all tokens for a user. + */ + @GET + @Path("/{accessTokenOwner}") + public Response getByOwner(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, decode(owner)); + return Response.ok(tokens).build(); + } + + /** + * Delete all existing access tokens for a user. + */ + @DELETE + @Path("/{accessTokenOwner}") + public Response delete(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, decode(owner)); + if (tokens == null || tokens.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + LOG.debug("About to delete accessTokens {}", Arrays.toString(tokens.toArray())); + accessTokenRepository.delete(tokens); + return Response.noContent().build(); + } + + private String decode(String owner) { + try { + owner = URLDecoder.decode(owner, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + LOG.error(String.format("Error while decoding '%s'", owner), e); + } + return owner; +} + + private List getAccessTokensForOwner(HttpServletRequest request, String owner) { + List accessTokens; + String userName = getUserId(request); + if (isAdminPrincipal(request) || owner.equals(userName )) { + accessTokens = accessTokenRepository.findByResourceOwnerId(owner); + LOG.debug("About to return all resource servers ({}) for owner {}", accessTokens.size(), owner); + } else { + accessTokens = new ArrayList<>(); + LOG.debug("User {} is neither admin nor owner. Returning empty list", userName); + } + return accessTokens; + } + + private List getAllAccessTokens(HttpServletRequest request) { + List accessTokens; + if (isAdminPrincipal(request)) { + accessTokens = addAll(accessTokenRepository.findAll().iterator()); + LOG.debug("About to return all resource servers ({}) for adminPrincipal", accessTokens.size()); + } else { + String owner = getUserId(request); + accessTokens = accessTokenRepository.findByResourceOwnerId(owner); + LOG.debug("About to return all resource servers ({}) for owner {}", accessTokens.size(), owner); + } + return accessTokens; + } + + +} diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java index e1453ca0..c61a825b 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/AccessTokenForOwnerResource.java @@ -16,9 +16,6 @@ package org.surfnet.oaaas.resource.resourceserver; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -73,62 +70,32 @@ public Response getAll(@Context HttpServletRequest request) { @GET @Path("/{accessTokenOwner}") public Response getByOwner(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { - return doGetByOwner(request, owner); + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, owner); + return Response.ok(tokens).build(); } -/** + /** * Delete all existing access tokens for a user. */ @DELETE @Path("/{accessTokenOwner}") public Response delete(@Context HttpServletRequest request, @PathParam("accessTokenOwner") String owner) { - return doDelete(request, owner); - } - - @GET - @Path("/{accessTokenOwnerEncrypted}") - public Response getByOwnerEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwnerEncrypted") String owner) { - return doGetByOwner(request, decode(owner)); - } - - @DELETE - @Path("/{accessTokenOwnerEncrypted}") - public Response deleteEncrypted(@Context HttpServletRequest request, @PathParam("accessTokenOwnerEncrypted") String owner) { - return doDelete(request, decode(owner)); - } - - private Response doGetByOwner(HttpServletRequest request, String owner) { - Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); - if (validateScopeResponse != null) { - return validateScopeResponse; - } - List tokens = getAccessTokensForOwner(request, owner); - return Response.ok(tokens).build(); - } - - private Response doDelete (HttpServletRequest request, String owner ) { - - Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); - if (validateScopeResponse != null) { - return validateScopeResponse; - } - List tokens = getAccessTokensForOwner(request, owner); - if (tokens == null || tokens.isEmpty()) { - return Response.status(Response.Status.NOT_FOUND).build(); - } - LOG.debug("About to delete accessTokens {}", Arrays.toString(tokens.toArray())); - accessTokenRepository.delete(tokens); - return Response.noContent().build(); + Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); + if (validateScopeResponse != null) { + return validateScopeResponse; + } + List tokens = getAccessTokensForOwner(request, owner); + if (tokens == null || tokens.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + LOG.debug("About to delete accessTokens {}", Arrays.toString(tokens.toArray())); + accessTokenRepository.delete(tokens); + return Response.noContent().build(); } - - private String decode(String owner) { - try { - owner = URLDecoder.decode(owner, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - LOG.error(String.format("Error while decoding '%s'", owner), e); - } - return owner; -} private List getAccessTokensForOwner(HttpServletRequest request, String owner) { List accessTokens; From c00bfc4ff30b02e190771bf42228dc8ceb53d685 Mon Sep 17 00:00:00 2001 From: Martin Haase Date: Mon, 23 Apr 2018 16:58:19 +0200 Subject: [PATCH 6/7] Test Commit --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index f4c3a765..e0c6f2c7 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ http://www.surfnet.nl + oharsta From bd6199ffacbafb30788c5c00b29ba0974f52aee3 Mon Sep 17 00:00:00 2001 From: Martin Haase Date: Mon, 23 Apr 2018 17:56:53 +0200 Subject: [PATCH 7/7] DAASI's Shibboleth Authenticator for APIs; to be used with a Shibboleth Service Provider --- apis-authorization-server-war/pom.xml | 10 ++ pom.xml | 2 +- shib-apis-authn/pom.xml | 59 ++++++++++ .../resources/saml.attributes.properties.dist | 17 +++ .../SAMLAuthenticatedPrincipal.java | 102 ++++++++++++++++ .../shib_apis_authn/ShibAuthenticator.java | 110 ++++++++++++++++++ .../de/daasi/shib_apis_authn/UserAgent.java | 39 +++++++ 7 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 shib-apis-authn/pom.xml create mode 100644 shib-apis-authn/resources/saml.attributes.properties.dist create mode 100644 shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/SAMLAuthenticatedPrincipal.java create mode 100644 shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/ShibAuthenticator.java create mode 100644 shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/UserAgent.java diff --git a/apis-authorization-server-war/pom.xml b/apis-authorization-server-war/pom.xml index f4adc635..978359ef 100644 --- a/apis-authorization-server-war/pom.xml +++ b/apis-authorization-server-war/pom.xml @@ -19,6 +19,11 @@ nl.surfnet.apis apis-authorization-server + + de.daasi + shib-apis-authn + 0.0.2-SNAPSHOT + com.sun.jersey jersey-servlet @@ -42,6 +47,11 @@ mysql mysql-connector-java + + net.sf.uadetector + uadetector-resources + 2014.04 + com.sun.jersey.contribs jersey-spring diff --git a/pom.xml b/pom.xml index e0c6f2c7..7393f043 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,6 @@ http://www.surfnet.nl - oharsta @@ -67,6 +66,7 @@ apis-resource-server-library apis-example-resource-server apis-authorization-server + shib-apis-authn apis-authorization-server-war apis-surfconext-authn apis-example-resource-server-war diff --git a/shib-apis-authn/pom.xml b/shib-apis-authn/pom.xml new file mode 100644 index 00000000..0c192b4b --- /dev/null +++ b/shib-apis-authn/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + ../pom.xml + nl.surfnet.apis + apis-parent + 1.3.6-SNAPSHOT + + + + de.daasi + shib-apis-authn + 0.0.2-SNAPSHOT + API Secure - Shibboleth authentication plugin + + + + org.surfnet.coin + spring-security-opensaml + + + commons-collections + commons-collections + + + + + nl.surfnet.apis + apis-authorization-server + 1.3.6-SNAPSHOT + + + org.surfnet.coin + coin-api-client + + + javax.servlet + javax.servlet-api + + + javax.inject + javax.inject + + + nl.surfnet.apis + apis-resource-server-library + 1.3.6-SNAPSHOT + + + net.sf.uadetector + uadetector-resources + 2014.04 + provided + + + + diff --git a/shib-apis-authn/resources/saml.attributes.properties.dist b/shib-apis-authn/resources/saml.attributes.properties.dist new file mode 100644 index 00000000..acfc756d --- /dev/null +++ b/shib-apis-authn/resources/saml.attributes.properties.dist @@ -0,0 +1,17 @@ +# +# APIs is protected by a Shibboleth SP now, like so: +# +# AuthType shibboleth +# ShibRequestSetting requireSession 1 +# require shib-session +# +# + +# REMOTE_USER is being used as principal. See shibboleth2.xml for which IdP attribute will make it up + +# could list further attributes driving displayname, admin role, etc. +# and implement that in ShibAuthenticator.java + +# Comma separated list of Admin Principals, short of a usable SAML attribute +adminPrincipals=user1@scope.edu,UserName2@anotherscope.edu + diff --git a/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/SAMLAuthenticatedPrincipal.java b/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/SAMLAuthenticatedPrincipal.java new file mode 100644 index 00000000..935018b8 --- /dev/null +++ b/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/SAMLAuthenticatedPrincipal.java @@ -0,0 +1,102 @@ +package de.daasi.shib_apis_authn; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.codehaus.jackson.annotate.JsonIgnore; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.CollectionUtils; +import org.surfnet.oaaas.auth.principal.AuthenticatedPrincipal; + +/** + * essentially the same code as org.surfnet.oaaas.conext.SAMLAuthenticatedPrincipal + * duplicated here in order to not introduce a dependency to conext stuff + */ +public class SAMLAuthenticatedPrincipal extends AuthenticatedPrincipal + implements UserDetails { + + @JsonIgnore + private final static String IDENTITY_PROVIDER = "IDENTITY_PROVIDER"; + + @JsonIgnore + private final static String DISPLAY_NAME = "DISPLAY_NAME"; + + public SAMLAuthenticatedPrincipal() { + } + + public SAMLAuthenticatedPrincipal(String username, + Collection roles, Map attributes, + Collection groups, String identityProvider, + String displayName, boolean adminPrincipal) { + super(username, roles, attributes, groups); + addAttribute(IDENTITY_PROVIDER, identityProvider); + addAttribute(DISPLAY_NAME, displayName); + setAdminPrincipal(adminPrincipal); + } + + @JsonIgnore + @Override + public Collection getAuthorities() { + ArrayList authorities = new ArrayList(); + if (!CollectionUtils.isEmpty(getRoles())) { + for (final String role : getRoles()) { + authorities.add(new GrantedAuthority() { + public String getAuthority() { + return role; + } + }); + } + } + return authorities; + } + + @JsonIgnore + @Override + public String getPassword() { + throw new RuntimeException( + "SAML based authentication does not support passwords on the receiving end"); + } + + @JsonIgnore + @Override + public String getUsername() { + return getName(); + } + + @Override + public String getDisplayName() { + return getAttributes().get(DISPLAY_NAME); + } + + @JsonIgnore + @Override + public boolean isAccountNonExpired() { + return true; + } + + @JsonIgnore + @Override + public boolean isAccountNonLocked() { + return true; + } + + @JsonIgnore + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @JsonIgnore + @Override + public boolean isEnabled() { + return true; + } + + @JsonIgnore + public String getIdentityProvider() { + return getAttributes().get(IDENTITY_PROVIDER); + } + +} diff --git a/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/ShibAuthenticator.java b/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/ShibAuthenticator.java new file mode 100644 index 00000000..92ba2d0b --- /dev/null +++ b/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/ShibAuthenticator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014, Martin Haase, DAASI International, Germany + * + * based on org.surfnet.oaaas.conext.SAMLAuthenticator and heavily reduced + * (essentially removed homegrown spring security based SP and group api stuff) + * + * 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. + */ + +package de.daasi.shib_apis_authn; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.stereotype.Component; +import org.surfnet.oaaas.auth.AbstractAuthenticator; + +@Component +public class ShibAuthenticator extends AbstractAuthenticator { + +// private static final Logger LOG = LoggerFactory +// .getLogger(ShibAuthenticator.class); + + private List adminList; + + private final Properties properties; + + { + try { + // Use Remote_user for Principal; only set Admin uids here; could extend by more + // attribute mappings, e.g. for DISPLAYNAME or roles. + properties = PropertiesLoaderUtils + .loadAllProperties("saml.attributes.properties"); + String[] admins = properties.getProperty("adminPrincipals") + .toLowerCase().split("\\s*,\\s*"); + adminList = Arrays.asList(admins); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + super.init(filterConfig); + } catch (Exception e) { + throw new ServletException(e); + } + } + + @Override + public boolean canCommence(HttpServletRequest request) { + return false; + } + + @Override + public void authenticate(HttpServletRequest request, + HttpServletResponse response, FilterChain chain, + String authStateValue, String returnUri) throws IOException, + ServletException { +// LOG.debug("OAuthCallback: " + isOAuthCallback(request)); + + String userId = request.getRemoteUser(); + if (StringUtils.isEmpty(userId)) { + throw new ServletException("No REMOTE_USER from Shibboleth SP!"); + } + boolean isAdmin = false; + if (adminList.contains(userId.toLowerCase())) { + isAdmin = true; + } + + // TODO: could populate SAML attributes + // HashMap attributes = new HashMap(); + + // populate User Agent details + UserAgent userAgent = new UserAgent(request); + HashMap attributes = userAgent.getAttributes(); + + SAMLAuthenticatedPrincipal principal = new SAMLAuthenticatedPrincipal( + userId, new ArrayList(), attributes, + new ArrayList(), null, userId, isAdmin); + + super.setPrincipal(request, principal); + super.setAuthStateValue(request, authStateValue); + chain.doFilter(request, response); + } + +} diff --git a/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/UserAgent.java b/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/UserAgent.java new file mode 100644 index 00000000..aca19be5 --- /dev/null +++ b/shib-apis-authn/src/main/java/de/daasi/shib_apis_authn/UserAgent.java @@ -0,0 +1,39 @@ +package de.daasi.shib_apis_authn; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import net.sf.uadetector.ReadableUserAgent; +import net.sf.uadetector.UserAgentStringParser; +import net.sf.uadetector.service.UADetectorServiceFactory; + +public class UserAgent { + + private HashMap attributes; + + public UserAgent (HttpServletRequest request) { + + UserAgentStringParser parser = UADetectorServiceFactory.getResourceModuleParser(); + ReadableUserAgent agent = parser.parse(request.getHeader("User-Agent")); + + attributes = new HashMap(); + attributes.put("platform", agent.getOperatingSystem().getName()); + attributes.put("model", agent.getVersionNumber().toVersionString()); + attributes.put("useragent", agent.getName()); + + Enumeration locales = request.getLocales(); + if (locales.hasMoreElements()) { // do not use 'while' because we only need the first language available + Locale firstLocale = (Locale) locales.nextElement(); + attributes.put("locale", firstLocale.toString()); // use full string de_DE_xxx_yyy + } else { + attributes.put("locale", "unknown"); + } + } + + public HashMap getAttributes () { + return attributes; + } +}