From 24ff802d3563cb4d9fdda0d3575dd8a522408b70 Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Thu, 10 Sep 2020 00:01:46 +0100 Subject: [PATCH 001/147] testing out SLU requirements --- src/main/webapp/local.html | 130 +++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/main/webapp/local.html diff --git a/src/main/webapp/local.html b/src/main/webapp/local.html new file mode 100644 index 00000000..9d4bccc4 --- /dev/null +++ b/src/main/webapp/local.html @@ -0,0 +1,130 @@ + + + + + + Mirador Viewer + + + + +
+ + + + + From 834959c9e82f406003c98c3d2326f62bf83d775c Mon Sep 17 00:00:00 2001 From: Glen Robson Date: Wed, 28 Oct 2020 00:45:42 +0000 Subject: [PATCH 002/147] Adding first auth version --- .gitignore | 1 + docker/sas-auth/Dockerfile | 22 +++ docker/sas-auth/docker-compose.yml | 20 +++ docker/sas-auth/sas.properties | 36 ++++ pom.xml | 6 + runDocker.sh | 3 + .../llgc/annotation/store/StoreConfig.java | 53 +++++- .../store/contollers/UserService.java | 78 +++++++++ .../annotation/store/data/login/Button.java | 21 +++ .../store/data/login/GenericOAuth.java | 46 +++++ .../store/data/login/OAuthTarget.java | 162 ++++++++++++++++++ .../store/data/login/UserMapping.java | 50 ++++++ .../annotation/store/data/users/User.java | 117 +++++++++++++ .../annotation/store/filters/OAuthFilter.java | 65 +++++++ .../store/servlets/login/LoginCallback.java | 76 ++++++++ .../store/servlets/login/OAuth.java | 65 +++++++ src/main/webapp/WEB-INF/templates/head.xhtml | 3 +- src/main/webapp/WEB-INF/templates/menu.xhtml | 55 ++++-- src/main/webapp/WEB-INF/web.xml | 36 +++- src/main/webapp/images/GitHub-logo.png | Bin 0 -> 1714 bytes src/main/webapp/images/GoogleLogo.svg | 21 +++ src/main/webapp/images/sas-gdmr-logo.png | Bin 0 -> 19926 bytes src/main/webapp/images/sas-logo.png | Bin 0 -> 12690 bytes src/main/webapp/login.xhtml | 24 +++ src/main/webapp/resources/css/local.css | 26 +++ 25 files changed, 961 insertions(+), 25 deletions(-) create mode 100644 docker/sas-auth/Dockerfile create mode 100644 docker/sas-auth/docker-compose.yml create mode 100644 docker/sas-auth/sas.properties create mode 100644 src/main/java/uk/org/llgc/annotation/store/contollers/UserService.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/data/login/Button.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/data/login/GenericOAuth.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/data/login/OAuthTarget.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/data/login/UserMapping.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/data/users/User.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/filters/OAuthFilter.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/servlets/login/LoginCallback.java create mode 100644 src/main/java/uk/org/llgc/annotation/store/servlets/login/OAuth.java create mode 100644 src/main/webapp/images/GitHub-logo.png create mode 100644 src/main/webapp/images/GoogleLogo.svg create mode 100644 src/main/webapp/images/sas-gdmr-logo.png create mode 100644 src/main/webapp/images/sas-logo.png create mode 100644 src/main/webapp/login.xhtml diff --git a/.gitignore b/.gitignore index 3d69a653..22a5873b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ cache index-2.6.1.html src/main/webapp/stats .aws-credentials +auth.json diff --git a/docker/sas-auth/Dockerfile b/docker/sas-auth/Dockerfile new file mode 100644 index 00000000..e945297e --- /dev/null +++ b/docker/sas-auth/Dockerfile @@ -0,0 +1,22 @@ +# build stage +FROM maven:3-jdk-11 AS buildstage +WORKDIR /usr/src/sas +COPY . /usr/src/sas +ARG MVN_ARGS="-DskipTests" +# build SAS using maven +RUN mvn $MVN_ARGS package + +# runnable container stage +FROM tomcat:9-jre11 AS runstage +# remove tomcat default webapps and create data directory +RUN rm -r /usr/local/tomcat/webapps/* +# copy SAS from build image +COPY --from=buildstage /usr/src/sas/target/simpleAnnotationStore /usr/local/tomcat/webapps/ROOT +# copy properties +COPY docker/sas-auth/sas.properties /usr/local/tomcat/webapps/ROOT/WEB-INF + +# Download auth config +RUN apt-get update +RUN apt-get install -y awscli +RUN aws s3 cp --region eu-west-2 s3://sasconfig/auth.json /usr/local/tomcat/webapps/ROOT/WEB-INF/ +# use default port and entrypoint diff --git a/docker/sas-auth/docker-compose.yml b/docker/sas-auth/docker-compose.yml new file mode 100644 index 00000000..db0026c0 --- /dev/null +++ b/docker/sas-auth/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3' +services: + web: + container_name: sas + build: + context: . + dockerfile: docker/sas-auth/Dockerfile + ports: + - "8888:8080" + elastic: + image: "elasticsearch:7.8.1" + container_name: elasticsearch + environment: + - discovery.type=single-node + ulimits: + memlock: + soft: -1 + hard: -1 + ports: + - 9200:9200 diff --git a/docker/sas-auth/sas.properties b/docker/sas-auth/sas.properties new file mode 100644 index 00000000..e875b597 --- /dev/null +++ b/docker/sas-auth/sas.properties @@ -0,0 +1,36 @@ +# Generic properties +# ================== + +# Uncomment this if you are behind a proxy or want a public URI +# baseURI=http://dev.llgc.org.uk/annotation/ + +# Uncomment this if you would like to use an encoder which will work on +# the annotation before its stored in the triplestore +# encoder=uk.org.llgc.annotation.store.encoders.BookOfPeaceEncode + +# if you are using Mirador versions greater than 2.1.4 then you need to uncomment the following +# as the annotation structure changed between versions +#encoder=uk.org.llgc.annotation.store.encoders.Mirador214 + +# Store configuration +# ================== + +# Uncomment this if you would like to use Jena as a backend +#store=jena +#data_dir=/annotation-data + +# Uncomment the following if you want to use Sesame +# store=sesame +# repo_url=http://localhost:8080/openrdf-sesame/repositories/test-anno + +# Uncomment the following if you want to use SOLR cores +#store=solr +#solr_connection=http://solr:8983/solr/annotations + +# Uncomment the following if you want to use SOLR collections (Cloud) +#store=solr-cloud +#solr_connection=http://solr:8983/solr,http://solr:7574/solr +#solr_collection=annotations + +store=elastic +elastic_connection=http://elasticsearch:9200/annotations diff --git a/pom.xml b/pom.xml index 9eb8ca9a..b072168a 100644 --- a/pom.xml +++ b/pom.xml @@ -183,6 +183,12 @@ 4.13 test + + + com.github.scribejava + scribejava-apis + 7.1.1 + simpleAnnotationStore diff --git a/runDocker.sh b/runDocker.sh index 129b6468..a187ee26 100755 --- a/runDocker.sh +++ b/runDocker.sh @@ -19,6 +19,9 @@ elif [[ "$backend" =~ [Cc]loud ]]; then elif [[ "$backend" =~ [Ee]lastic ]]; then echo "Running SAS with Elastic Cloud on port 8888" docker-compose -f docker/sas-elastic/docker-compose.yml --project-directory . up +elif [[ "$backend" =~ [Aa]uth ]]; then + echo "Running SAS with Elastic Cloud on port 8888 with Auth enabled" + docker-compose -f docker/sas-auth/docker-compose.yml --project-directory . up else echo "I don't recognise '$backend'. Options are Jena / Solr / Cloud" fi diff --git a/src/main/java/uk/org/llgc/annotation/store/StoreConfig.java b/src/main/java/uk/org/llgc/annotation/store/StoreConfig.java index 53c759d6..fd3f39f1 100644 --- a/src/main/java/uk/org/llgc/annotation/store/StoreConfig.java +++ b/src/main/java/uk/org/llgc/annotation/store/StoreConfig.java @@ -7,6 +7,8 @@ import java.util.Map; import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; import java.util.Enumeration; import java.util.Properties; @@ -17,6 +19,7 @@ import uk.org.llgc.annotation.store.adapters.elastic.ElasticStore; import uk.org.llgc.annotation.store.encoders.Encoder; import uk.org.llgc.annotation.store.AnnotationUtils; +import uk.org.llgc.annotation.store.data.login.OAuthTarget; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -32,11 +35,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import com.github.jsonldjava.utils.JsonUtils; + public class StoreConfig extends HttpServlet { protected static Logger _logger = LogManager.getLogger(StoreConfig.class.getName()); protected Map _props = null; public final String[] ALLOWED_PROPS = {"baseURI","encoder","store","data_dir","store","repo_url","solr_connection","elastic_connection"}; protected AnnotationUtils _annotationUtils = null; + protected List _authTargets = null; public StoreConfig() { _props = null; @@ -73,9 +79,50 @@ public void init(final ServletConfig pConfig) throws ServletException { this.overloadConfigFromEnviroment(tProps); _annotationUtils = new AnnotationUtils(this.getRealPath("/contexts"), getEncoder()); + try { + this.loadAuthConfig(this.getServletContext().getResourceAsStream("/WEB-INF/auth.json")); + } catch (IOException tExcpt) { + tExcpt.printStackTrace(); + throw new ServletException("Failed to load auth config file due to: " + tExcpt.getMessage()); + } initConfig(this); } + protected void loadAuthConfig(final InputStream pConfigFile) throws IOException { + if (pConfigFile == null) { + _authTargets = null; + } else { + Object tObject = JsonUtils.fromInputStream(pConfigFile); + _authTargets = new ArrayList(); + if (tObject instanceof Map) { + _authTargets.add(new OAuthTarget((Map)tObject)); + } else { + List> tConfigs = (List>)tObject; + for (Map tConfig : tConfigs) { + _authTargets.add(new OAuthTarget(tConfig)); + } + } + } + } + + // Is auth setup? + public boolean isAuth() { + return _authTargets != null; + } + + public List getAuthTargets() { + return _authTargets; + } + + public OAuthTarget getAuthTarget(final String pType) { + for (OAuthTarget tTarget : _authTargets) { + if (tTarget.getId().equals(pType)) { + return tTarget; + } + } + return null; // no target found + } + public AnnotationUtils getAnnotationUtils() { return _annotationUtils; } @@ -128,9 +175,13 @@ public String getBaseURI(final HttpServletRequest pReq) { String tServletName = ""; if (pReq.getServletPath().matches(".*/[a-zA-Z0-9.]*$")) { tServletName = pReq.getServletPath().replaceAll("/[a-zA-Z0-9.]*$","").replaceAll("/",""); + if (tServletName.isEmpty()) { + tServletName = pReq.getServletPath().replaceAll("/",""); + } } else { - tServletName = pReq.getServletPath().replaceAll("/",""); + tServletName = pReq.getServletPath().replaceAll("\\/",""); } + for (int i = tURL.length - 1; i >=0 ; i--) { if (tURL[i].equals(tServletName)) { tBase = i; diff --git a/src/main/java/uk/org/llgc/annotation/store/contollers/UserService.java b/src/main/java/uk/org/llgc/annotation/store/contollers/UserService.java new file mode 100644 index 00000000..a77985c3 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/contollers/UserService.java @@ -0,0 +1,78 @@ +package uk.org.llgc.annotation.store.contollers; + +import javax.faces.bean.ManagedBean; +import javax.faces.bean.RequestScoped; +import javax.annotation.PostConstruct; + +import javax.faces.context.FacesContext; + +import javax.servlet.http.HttpSession; + +import uk.org.llgc.annotation.store.data.users.User; +import uk.org.llgc.annotation.store.data.login.OAuthTarget; +import uk.org.llgc.annotation.store.StoreConfig; + +import java.util.List; + +@RequestScoped +@ManagedBean +public class UserService { + + protected HttpSession _session = null; + + public UserService() { + } + + public UserService(final HttpSession pSession) { + _session = pSession; + } + + @PostConstruct + public void init() { + } + + protected HttpSession getSession() { + if (_session == null) { + FacesContext facesContext = FacesContext.getCurrentInstance(); + HttpSession tSession = (HttpSession)facesContext.getExternalContext().getSession(true); + return tSession; + } else { + return _session; + } + } + + public void setUser(final User pUser) { + this.getSession().setAttribute("user", pUser); + } + + public User getUser() { + HttpSession tSession = this.getSession(); + if (tSession.getAttribute("user") != null) { + return (User)tSession.getAttribute("user"); + } else { + return null; + } + } + + public boolean isAuthenticated() { + return this.getSession().getAttribute("user") != null; + } + + public boolean getAuthenticated() { + return isAuthenticated(); + } + + public List getConfig() { + return StoreConfig.getConfig().getAuthTargets(); + } + + public boolean isAdmin() { + HttpSession tSession = this.getSession(); + if (tSession.getAttribute("user") == null) { + // No one is logged in + return false; + } + User tUser = (User)tSession.getAttribute("user"); + return tUser.isAdmin(); + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/data/login/Button.java b/src/main/java/uk/org/llgc/annotation/store/data/login/Button.java new file mode 100644 index 00000000..f50f5972 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/data/login/Button.java @@ -0,0 +1,21 @@ +package uk.org.llgc.annotation.store.data.login; + +import java.util.Map; + +public class Button { + protected String _logo = ""; + protected String _text = ""; + + public Button(final Map pConfig) { + _logo = pConfig.get("logo"); + _text = pConfig.get("text"); + } + + public String getLogo() { + return _logo; + } + + public String getText() { + return _text; + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/data/login/GenericOAuth.java b/src/main/java/uk/org/llgc/annotation/store/data/login/GenericOAuth.java new file mode 100644 index 00000000..37245188 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/data/login/GenericOAuth.java @@ -0,0 +1,46 @@ +package uk.org.llgc.annotation.store.data.login; + +import com.github.scribejava.core.builder.api.DefaultApi20; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth2.bearersignature.BearerSignature; +import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter; + +import java.util.Map; + +public class GenericOAuth extends DefaultApi20 { + + protected String _accessTokenEndpoint = ""; + protected Verb _accessTokenVerb = null; + protected String _authBaseUrl = ""; + + public GenericOAuth(final Map pConfig) { + _accessTokenEndpoint = pConfig.get("accessTokenEndpoint"); + if (pConfig.get("accessTokenVerb").toUpperCase().equals("POST")) { + _accessTokenVerb = Verb.POST; + } else { + _accessTokenVerb = Verb.GET; + } + _authBaseUrl = pConfig.get("authorizationBaseUrl"); + } + + @Override + public Verb getAccessTokenVerb() { + return Verb.POST; + } + + // From: https://wp-oauth.com/docs/general/main-concepts/ + @Override + public String getAccessTokenEndpoint() { + return "https://catalog.metascripta.org/oauth/token"; + } + + @Override + protected String getAuthorizationBaseUrl() { + return "https://catalog.metascripta.org/oauth/authorize"; + } + + @Override + public BearerSignature getBearerSignature() { + return BearerSignatureURIQueryParameter.instance(); + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/data/login/OAuthTarget.java b/src/main/java/uk/org/llgc/annotation/store/data/login/OAuthTarget.java new file mode 100644 index 00000000..baaafac7 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/data/login/OAuthTarget.java @@ -0,0 +1,162 @@ +package uk.org.llgc.annotation.store.data.login; + +import java.lang.reflect.Method; + +import java.util.Map; + +import com.github.scribejava.core.builder.api.DefaultApi20; +import com.github.scribejava.core.model.Verb; + +import java.io.IOException; + +public class OAuthTarget { + protected String _id = ""; + protected DefaultApi20 _endpoints = null; + protected String _clientId = ""; + protected String _clientSecret = ""; + protected String _scope = null; + protected Map _additionalParams = null; + protected Button _button = null; + protected UserMapping _mapping = null; + + public OAuthTarget(final Map pConfig) throws IOException { + this.setId(pConfig); + this.setEndpoints(pConfig); + this.setClientId(pConfig); + this.setClientSecret(pConfig); + this.setScopes(pConfig); + this.setAdditionalParams(pConfig); + this.setButton(pConfig); + this.setMapping(this.getId(), pConfig); + } + + + /** + * Get id. + * + * @return id as String. + */ + public String getId() { + return _id; + } + + protected void setId(final Map pConfig) { + _id = (String)pConfig.get("id"); + } + + /** + * Get endpoints. + * + * @return endpoints as DefaultApi20. + */ + public DefaultApi20 getEndpoints() { + return _endpoints; + } + + protected void setEndpoints(final Map pConfig) throws IOException { + if (((String)pConfig.get("class")).equals("uk.org.llgc.annotation.store.data.login.GenericOAuth")) { + _endpoints = new GenericOAuth((Map)pConfig.get("endpoints")); + } else { + Class tClass = null; + try { + tClass = Class.forName((String)pConfig.get("class")); + } catch (ClassNotFoundException tExcpt) { + throw new IOException("Failed to load auth class " + (String)pConfig.get("class") + " due to " + tExcpt); + } catch (LinkageError tExcpt) { + throw new IOException("Failed to load auth class " + (String)pConfig.get("class") + " due to " + tExcpt); + } + Method tMethod = null; + try { + tMethod = tClass.getMethod("instance", null); + } catch (NoSuchMethodException tExcpt) { + throw new IOException("Failed to load auth class " + (String)pConfig.get("class") + " due to problem loading the instance() method: " + tExcpt); + } + + try { + _endpoints = (DefaultApi20)tMethod.invoke(null); + } catch (ExceptionInInitializerError tExcpt) { + throw new IOException("Failed to load auth class " + (String)pConfig.get("class") + " due to problem running the instance() method: " + tExcpt); + } catch (ReflectiveOperationException tExcpt) { + throw new IOException("Failed to load auth class " + (String)pConfig.get("class") + " due to problem running the instance() method: " + tExcpt); + } + } + } + + /** + * Get clientId. + * + * @return clientId as String. + */ + public String getClientId() { + return _clientId; + } + + protected void setClientId(final Map pConfig) { + _clientId = (String)pConfig.get("clientId"); + } + + /** + * Get clientSecret. + * + * @return clientSecret as String. + */ + public String getClientSecret() { + return _clientSecret; + } + + protected void setClientSecret(final Map pConfig) { + _clientSecret = (String)pConfig.get("clientSecret"); + } + + public String getScopes() { + return _scope; + } + + public void setScopes(final Map pConfig) { + if (pConfig.get("scope") != null) { + _scope = (String)pConfig.get("scope"); + } else { + _scope = null; + } + } + + public Map getAdditionalParams() { + return _additionalParams; + } + + public void setAdditionalParams(final Map pConfig) { + if (pConfig.get("additionalParam") != null) { + _additionalParams = (Map)pConfig.get("additionalParam"); + } else { + _additionalParams = null; // Optional config + } + } + + + /** + * Get button. + * + * @return button as Button. + */ + public Button getButton() { + return _button; + } + + protected void setButton(final Map pConfig) { + _button = new Button((Map)pConfig.get("button")); + } + + /** + * Get mapping. + * + * @return mapping as UserMapping. + */ + public UserMapping getMapping() { + return _mapping; + } + + protected void setMapping(final String pType, final Map pConfig) { + _mapping = new UserMapping(pType, (Map)pConfig.get("userMapping")); + } + +} diff --git a/src/main/java/uk/org/llgc/annotation/store/data/login/UserMapping.java b/src/main/java/uk/org/llgc/annotation/store/data/login/UserMapping.java new file mode 100644 index 00000000..bd7dee48 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/data/login/UserMapping.java @@ -0,0 +1,50 @@ +package uk.org.llgc.annotation.store.data.login; + +import java.util.Map; + +import uk.org.llgc.annotation.store.data.users.User; + +public class UserMapping { + protected String _type = ""; + protected String _endpoint = ""; + protected Map _mapping = null; + public UserMapping(final String pType, final Map pConfig) { + _type = pType; + _endpoint = (String)pConfig.get("endpoint"); + + _mapping = (Map)pConfig.get("responseKeys"); + } + + public String getEndpoint() { + return _endpoint; + } + + public User createUser(final Map pResponse) { + User tUser = new User(); + tUser.setId(_type + "_" + this.getKey("id", pResponse)); + + if (this.isIn("name", pResponse)) { + tUser.setName(this.getKey("name", pResponse)); + } + if (this.isIn("email", pResponse)) { + tUser.setEmail(this.getKey("email", pResponse)); + } + if (this.isIn("pic", pResponse)) { + tUser.setPicture(this.getKey("pic", pResponse)); + } + + return tUser; + } + + protected boolean isIn(final String pKey, final Map pMap) { + if (_mapping.get(pKey) != null) { + return pMap.get(_mapping.get(pKey)) != null; + } else { + return false; + } + } + + protected String getKey(final String pKey, final Map pMap) { + return pMap.get(_mapping.get(pKey)).toString(); + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/data/users/User.java b/src/main/java/uk/org/llgc/annotation/store/data/users/User.java new file mode 100644 index 00000000..6ee5eea7 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/data/users/User.java @@ -0,0 +1,117 @@ +package uk.org.llgc.annotation.store.data.users; + +import com.github.scribejava.core.model.OAuth2AccessToken; + +public class User { + protected String _id = ""; + protected String _name = ""; + protected String _email = ""; + protected String _pic = ""; + protected boolean _isAdmin = false; + protected OAuth2AccessToken _token = null; + + public User() { + } + + public boolean isAdmin() { + return _isAdmin; + } + + public void setAdmin(final boolean pValue) { + _isAdmin = pValue; + } + + public String toString() { + return "Id: " + _id + "\nName: " + _name + "\nEmail: " + _email + "\nPic: " + _pic; + } + + /** + * Get id. + * + * @return id as String. + */ + public String getId() { + return _id; + } + + /** + * Set id. + * + * @param id the value to set. + */ + public void setId(final String pId) { + _id = pId; + } + + /** + * Get name. + * + * @return name as String. + */ + public String getName() { + return _name; + } + + /** + * Set name. + * + * @param name the value to set. + */ + public void setName(final String pName) { + _name = pName; + } + + /** + * Get email. + * + * @return email as String. + */ + public String getEmail() { + return _email; + } + + /** + * Set email. + * + * @param email the value to set. + */ + public void setEmail(final String pEmail) { + _email = pEmail; + } + + /** + * Get pic. + * + * @return pic as String. + */ + public String getPicture() { + return _pic; + } + + /** + * Set pic. + * + * @param pic the value to set. + */ + public void setPicture(final String pPic) { + _pic = pPic; + } + + /** + * Get token. + * + * @return token as OAuth2AccessToken. + */ + public OAuth2AccessToken getToken() { + return _token; + } + + /** + * Set token. + * + * @param token the value to set. + */ + public void setToken(final OAuth2AccessToken pToken) { + _token = pToken; + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/filters/OAuthFilter.java b/src/main/java/uk/org/llgc/annotation/store/filters/OAuthFilter.java new file mode 100644 index 00000000..92bab19a --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/filters/OAuthFilter.java @@ -0,0 +1,65 @@ +package uk.org.llgc.annotation.store.filters; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.ServletException; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletRequest; + +import java.io.IOException; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; + +import java.util.Random; +import java.util.Map; +import java.util.HashMap; + +import uk.org.llgc.annotation.store.servlets.login.LoginCallback; +import uk.org.llgc.annotation.store.contollers.UserService; +import uk.org.llgc.annotation.store.StoreConfig; + +public class OAuthFilter implements Filter { + + public void init(final FilterConfig filterConfig) { + } + + public void doFilter(final ServletRequest pRequest, final ServletResponse pRes, final FilterChain pChain) throws IOException, ServletException { + HttpServletRequest pReq = (HttpServletRequest)pRequest; + if (StoreConfig.getConfig().isAuth()) { + HttpSession tSession = pReq.getSession(); + UserService tUsers = new UserService(tSession); + if (!tUsers.isAuthenticated()) { + String tCallingURL = pReq.getRequestURI(); + if (pReq.getQueryString() != null) { + tCallingURL += "?" + pReq.getQueryString(); + } + + tSession.setAttribute("oauth_url", tCallingURL); + if (StoreConfig.getConfig().getAuthTargets().size() == 1) { + // if there is only 1 target forward straight onto the oauth process + ((HttpServletResponse)pRes).sendRedirect("/login?type=" + StoreConfig.getConfig().getAuthTargets().get(0).getId()); + } else { + // Otherwise ask the user how they want to authenticate + ((HttpServletResponse)pRes).sendRedirect("/login.xhtml"); + } + return; + } else { + System.out.println("Found user: " + tSession.getAttribute("user")); + } + } + pChain.doFilter(pReq, pRes); + } + + public void destroy() { + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/servlets/login/LoginCallback.java b/src/main/java/uk/org/llgc/annotation/store/servlets/login/LoginCallback.java new file mode 100644 index 00000000..308d9583 --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/servlets/login/LoginCallback.java @@ -0,0 +1,76 @@ +package uk.org.llgc.annotation.store.servlets.login; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpSession; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.ExecutionException; + +import java.io.IOException; + +import java.util.Collections; +import java.util.Random; +import java.util.Map; + +import uk.org.llgc.annotation.store.data.users.User; +import uk.org.llgc.annotation.store.data.login.OAuthTarget; +import uk.org.llgc.annotation.store.contollers.UserService; +import uk.org.llgc.annotation.store.StoreConfig; + +import com.github.jsonldjava.utils.JsonUtils; + +public class LoginCallback extends HttpServlet { + protected static Logger _logger = LogManager.getLogger(LoginCallback.class.getName()); + + public void init(final ServletConfig pConfig) throws ServletException { + super.init(pConfig); + } + + public void doGet(final HttpServletRequest pReq, final HttpServletResponse pRes) throws IOException { + for (String tKey : Collections.list(pReq.getParameterNames())) { + System.out.println("Key: " + tKey + "\tValue: " + pReq.getParameter(tKey)); + } + HttpSession tSession = pReq.getSession(); + System.out.println("Original URL: " + tSession.getAttribute("oauth_url")); + System.out.println("Secret match (" + pReq.getParameter("state") + " = " + tSession.getAttribute("oauth_state") + ") is " + pReq.getParameter("state").equals(tSession.getAttribute("oauth_state"))); + try { + OAuthTarget tTarget = (OAuthTarget)tSession.getAttribute("oauth_target"); + final OAuth20Service service = new ServiceBuilder(tTarget.getClientId()) + .apiSecret(tTarget.getClientSecret()) + .callback(StoreConfig.getConfig().getBaseURI(pReq) + "/login-callback") + .build(tTarget.getEndpoints()); + + OAuth2AccessToken accessToken = service.getAccessToken(pReq.getParameter("code")); + + final OAuthRequest request = new OAuthRequest(Verb.GET, tTarget.getMapping().getEndpoint()); + service.signRequest(accessToken, request); + Response tResponse = service.execute(request); + User tUser = tTarget.getMapping().createUser((Map)JsonUtils.fromString(tResponse.getBody())); + tUser.setToken(accessToken); + + UserService tUsers = new UserService(tSession); + tUsers.setUser(tUser); + + pRes.sendRedirect((String)tSession.getAttribute("oauth_url")); + } catch (InterruptedException tExcpt) { + tExcpt.printStackTrace(); + } catch (ExecutionException tExcpt) { + tExcpt.printStackTrace(); + // should redirect to login fail page + } + } +} diff --git a/src/main/java/uk/org/llgc/annotation/store/servlets/login/OAuth.java b/src/main/java/uk/org/llgc/annotation/store/servlets/login/OAuth.java new file mode 100644 index 00000000..b9c6c59d --- /dev/null +++ b/src/main/java/uk/org/llgc/annotation/store/servlets/login/OAuth.java @@ -0,0 +1,65 @@ +package uk.org.llgc.annotation.store.servlets.login; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpSession; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.oauth.OAuth20Service; + +import java.util.Random; +import java.util.Map; +import java.util.HashMap; + +import java.io.IOException; + +import uk.org.llgc.annotation.store.StoreConfig; +import uk.org.llgc.annotation.store.data.login.OAuthTarget; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class OAuth extends HttpServlet { + protected static Logger _logger = LogManager.getLogger(OAuth.class.getName()); + + public void init(final ServletConfig pConfig) throws ServletException { + super.init(pConfig); + } + + public void doGet(final HttpServletRequest pReq, final HttpServletResponse pRes) throws IOException { + HttpSession tSession = pReq.getSession(); + if (pReq.getParameter("type") != null) { + OAuthTarget tTarget = StoreConfig.getConfig().getAuthTarget(pReq.getParameter("type")); + System.out.println("Callback URL: " + StoreConfig.getConfig().getBaseURI(pReq) + "/login-callback"); + if (tTarget != null) { + final String secretState = tTarget.getId() + new Random().nextInt(999_999); + final OAuth20Service service = new ServiceBuilder(tTarget.getClientId()) + .apiSecret(tTarget.getClientSecret()) + .defaultScope(tTarget.getScopes()) // replace with desired scope + .callback(StoreConfig.getConfig().getBaseURI(pReq) + "/login-callback") + .build(tTarget.getEndpoints()); + + Map additionalParams = new HashMap<>(); + if (tTarget.getAdditionalParams() != null) { + additionalParams = tTarget.getAdditionalParams(); + } + //force to reget refresh token (if user are asked not the first time) + //additionalParams.put("prompt", "consent"); + final String authorizationUrl = service.createAuthorizationUrlBuilder() + .state(secretState) + .additionalParams(additionalParams) + .build(); + tSession.setAttribute("oauth_state", secretState); + tSession.setAttribute("oauth_target", tTarget); + pRes.sendRedirect(authorizationUrl); + } else { + // Type parameter unrecognised + } + } else { + // No type parameter sent + } + } +} diff --git a/src/main/webapp/WEB-INF/templates/head.xhtml b/src/main/webapp/WEB-INF/templates/head.xhtml index 1c0d96e6..e4d01342 100644 --- a/src/main/webapp/WEB-INF/templates/head.xhtml +++ b/src/main/webapp/WEB-INF/templates/head.xhtml @@ -7,7 +7,8 @@ #{title} - + + diff --git a/src/main/webapp/WEB-INF/templates/menu.xhtml b/src/main/webapp/WEB-INF/templates/menu.xhtml index ec94fdf4..6fcb6398 100644 --- a/src/main/webapp/WEB-INF/templates/menu.xhtml +++ b/src/main/webapp/WEB-INF/templates/menu.xhtml @@ -6,34 +6,53 @@ xmlns:ui="http://xmlns.jcp.org/jsf/facelets"> - diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 64fecf9a..fa7f76b0 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -11,10 +11,18 @@ AuthFilter uk.org.llgc.annotation.store.filters.OAuthFilter + AuthFilter /local.html + /auth.xhtml @@ -106,6 +114,18 @@ Login uk.org.llgc.annotation.store.servlets.login.OAuth + + Logout + uk.org.llgc.annotation.store.servlets.login.Logout + + post_logout_url + /auth.xhtml + + + + Logout + /logout + Login /login diff --git a/src/main/webapp/auth.xhtml b/src/main/webapp/auth.xhtml new file mode 100644 index 00000000..fefa7760 --- /dev/null +++ b/src/main/webapp/auth.xhtml @@ -0,0 +1,32 @@ + + + + + + + + + +