This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-xing-oauth.git
commit 1a04fbbee1d8190fed60c5933a3cc134c677e2a1 Author: Oliver Lietz <[email protected]> AuthorDate: Fri Jul 4 15:43:38 2014 +0000 SLING-3731 SLING-3732 add Sling Authentication XING API, Sling Authentication XING OAuth and Sling Authentication XING Login git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1607877 13f79535-47bb-0310-9956-ffa450edef68 --- README.md | 5 + pom.xml | 197 +++++++++++++++++ .../apache/sling/auth/xing/oauth/XingOauth.java | 31 +++ .../auth/xing/oauth/XingOauthUserManager.java | 27 +++ .../sling/auth/xing/oauth/XingOauthUtil.java | 51 +++++ .../oauth/impl/DefaultXingOauthUserManager.java | 182 ++++++++++++++++ .../oauth/impl/XingOauthAuthenticationHandler.java | 239 +++++++++++++++++++++ .../oauth/impl/XingOauthAuthenticationPlugin.java | 69 ++++++ .../oauth/impl/XingOauthLoginModulePlugin.java | 122 +++++++++++ .../apache/sling/auth/xing/oauth/package-info.java | 20 ++ .../OSGI-INF/metatype/metatype.properties | 44 ++++ 11 files changed, 987 insertions(+) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d688ec7 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +Apache Sling Authentication XING OAuth +====================================== + +* uses the [XING API](https://dev.xing.com/docs) with OAuth 1.0 for [authentication](https://dev.xing.com/docs/authentication) +* allows creating and updating JCR users based on supplied user data from XING diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..267e5a0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>19</version> + <relativePath>../../../parent/pom.xml</relativePath> + </parent> + + <artifactId>org.apache.sling.auth.xing.oauth</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Authentication XING OAuth</name> + <description>Apache Sling Authentication XING OAuth</description> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <sling.java.version>6</sling.java.version> + </properties> + + <dependencies> + <!-- javax --> + <dependency> + <groupId>javax.jcr</groupId> + <artifactId>jcr</artifactId> + <version>2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <scope>provided</scope> + </dependency> + <!-- OSGi --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + <scope>provided</scope> + </dependency> + <!-- Apache Commons --> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.6</version> + <scope>provided</scope> + </dependency> + <!-- Apache Sling --> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.api</artifactId> + <version>2.7.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.auth.core</artifactId> + <version>1.1.6</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.auth.xing.api</artifactId> + <version>0.0.1-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.osgi</artifactId> + <version>2.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.api</artifactId> + <version>2.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.jackrabbit.server</artifactId> + <version>2.1.2</version> + <scope>provided</scope> + </dependency> + <!-- Apache Jackrabbit --> + <dependency> + <groupId>org.apache.jackrabbit</groupId> + <artifactId>jackrabbit-api</artifactId> + <version>2.0.0</version> + <scope>provided</scope> + </dependency> + <!-- Apache Felix --> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + <scope>provided</scope> + </dependency> + <!-- bndlib --> + <dependency> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bndlib</artifactId> + <version>2.3.0</version> + <scope>provided</scope> + </dependency> + <!-- Scribe OAuth --> + <dependency> + <groupId>org.scribe</groupId> + <artifactId>scribe</artifactId> + <version>1.3.5</version> + <scope>compile</scope> + </dependency> + <!-- Gson --> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.2.4</version> + <scope>compile</scope> + </dependency> + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.7</version> + <scope>provided</scope> + </dependency> + <!-- testing --> + <dependency> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + <version>6.8.8</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-Category>sling</Bundle-Category> + <Embed-Dependency> + *;scope=compile;inline=true, + org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/PropertiesUtil.*" + </Embed-Dependency> + <_removeheaders> + Embed-Dependency, + Private-Package, + Include-Resource + </_removeheaders> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>scr</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/XingOauth.java b/src/main/java/org/apache/sling/auth/xing/oauth/XingOauth.java new file mode 100644 index 0000000..944add2 --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/XingOauth.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth; + +public class XingOauth { + + public static final String AUTH_TYPE = "xing-oauth"; + + public static final String AUTHENTICATION_CREDENTIALS_ACCESS_TOKEN_KEY = "xing-access-token"; + + public static final String AUTHENTICATION_CREDENTIALS_USER_KEY = "xing-user"; + + public static final String SERVICE_VENDOR = "The Apache Software Foundation"; + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/XingOauthUserManager.java b/src/main/java/org/apache/sling/auth/xing/oauth/XingOauthUserManager.java new file mode 100644 index 0000000..6d158f4 --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/XingOauthUserManager.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth; + +import aQute.bnd.annotation.ProviderType; +import org.apache.sling.auth.xing.api.XingUserManager; + +@ProviderType +public interface XingOauthUserManager extends XingUserManager { + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/XingOauthUtil.java b/src/main/java/org/apache/sling/auth/xing/oauth/XingOauthUtil.java new file mode 100644 index 0000000..81c2f21 --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/XingOauthUtil.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth; + +import javax.jcr.Credentials; +import javax.jcr.SimpleCredentials; + +import org.apache.sling.auth.xing.api.XingUser; +import org.scribe.model.Token; + +public class XingOauthUtil { + + public static Token getAccessToken(Credentials credentials) { + if (credentials instanceof SimpleCredentials) { + final SimpleCredentials simpleCredentials = (SimpleCredentials) credentials; + final Object attribute = simpleCredentials.getAttribute(XingOauth.AUTHENTICATION_CREDENTIALS_ACCESS_TOKEN_KEY); + if (attribute instanceof Token) { + return (Token) attribute; + } + } + return null; + } + + public static XingUser getXingUser(Credentials credentials) { + if (credentials instanceof SimpleCredentials) { + final SimpleCredentials simpleCredentials = (SimpleCredentials) credentials; + final Object attribute = simpleCredentials.getAttribute(XingOauth.AUTHENTICATION_CREDENTIALS_USER_KEY); + if (attribute instanceof XingUser) { + return (XingUser) attribute; + } + } + return null; + } + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/impl/DefaultXingOauthUserManager.java b/src/main/java/org/apache/sling/auth/xing/oauth/impl/DefaultXingOauthUserManager.java new file mode 100644 index 0000000..a82d47e --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/impl/DefaultXingOauthUserManager.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth.impl; + +import java.util.Dictionary; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFactory; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.sling.auth.xing.api.AbstractXingUserManager; +import org.apache.sling.auth.xing.api.XingUser; +import org.apache.sling.auth.xing.oauth.XingOauth; +import org.apache.sling.auth.xing.oauth.XingOauthUserManager; +import org.apache.sling.auth.xing.oauth.XingOauthUtil; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.apache.sling.jcr.api.SlingRepository; +import org.osgi.framework.Constants; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + label = "Apache Sling Authentication XING OAuth “Default User Manager”", + description = "Default User Manager for Sling Authentication XING OAuth", + immediate = true, + metatype = true +) +@Service +@Properties({ + @Property(name = Constants.SERVICE_VENDOR, value = XingOauth.SERVICE_VENDOR), + @Property(name = Constants.SERVICE_DESCRIPTION, value = "Default User Manager for Sling Authentication XING OAuth"), + @Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = false) +}) +public class DefaultXingOauthUserManager extends AbstractXingUserManager implements XingOauthUserManager { + + @Reference + private SlingRepository slingRepository; + + private static final String FIRSTNAME_PROPERTY = "firstname"; + + private static final String LASTNAME_PROPERTY = "lastname"; + + @Property(boolValue = DEFAULT_AUTO_CREATE_USER) + private static final String AUTO_CREATE_USER_PARAMETER = "org.apache.sling.auth.xing.oauth.impl.DefaultXingOauthUserManager.user.create.auto"; + + @Property(boolValue = DEFAULT_AUTO_UPDATE_USER) + private static final String AUTO_UPDATE_USER_PARAMETER = "org.apache.sling.auth.xing.oauth.impl.DefaultXingOauthUserManager.user.update.auto"; + + private final Logger logger = LoggerFactory.getLogger(DefaultXingOauthUserManager.class); + + public DefaultXingOauthUserManager() { + } + + @Activate + protected void activate(final ComponentContext componentContext) { + logger.debug("activate"); + configure(componentContext); + } + + @Modified + protected void modified(final ComponentContext componentContext) { + logger.debug("modified"); + configure(componentContext); + } + + @Deactivate + protected void deactivate(final ComponentContext componentContext) { + logger.debug("deactivate"); + if (session != null) { + session.logout(); + session = null; + } + } + + protected synchronized void configure(final ComponentContext componentContext) { + final Dictionary properties = componentContext.getProperties(); + autoCreateUser = PropertiesUtil.toBoolean(properties.get(AUTO_CREATE_USER_PARAMETER), DEFAULT_AUTO_CREATE_USER); + autoUpdateUser = PropertiesUtil.toBoolean(properties.get(AUTO_UPDATE_USER_PARAMETER), DEFAULT_AUTO_UPDATE_USER); + } + + @Override + protected SlingRepository getSlingRepository() { + return slingRepository; + } + + @Override + public User createUser(final Credentials credentials) { + logger.debug("create user"); + final XingUser xingUser = XingOauthUtil.getXingUser(credentials); + if (xingUser == null) { + return null; + } + + try { + final String userId = xingUser.getId(); // TODO make configurable + + final Session session = getSession(); + final UserManager userManager = getUserManager(session); + final User user = userManager.createUser(userId, null); + + // TODO disable user on create? + final ValueFactory valueFactory = session.getValueFactory(); + final Value firstnameValue = valueFactory.createValue(xingUser.getFirstName()); + final Value lastnameValue = valueFactory.createValue(xingUser.getLastName()); + user.setProperty(FIRSTNAME_PROPERTY, firstnameValue); + user.setProperty(LASTNAME_PROPERTY, lastnameValue); + session.save(); + return user; + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } + } + + @Override + public User updateUser(Credentials credentials) { + logger.debug("update user"); + final XingUser xingUser = XingOauthUtil.getXingUser(credentials); + if (xingUser == null) { + return null; + } + + try { + final Session session = getSession(); + final User user = getUser(credentials); + final ValueFactory valueFactory = session.getValueFactory(); + + final boolean firstnameUpdated = updateUserProperty(user, valueFactory, FIRSTNAME_PROPERTY, xingUser.getFirstName()); + final boolean lastnameUpdated = updateUserProperty(user, valueFactory, LASTNAME_PROPERTY, xingUser.getLastName()); + if (firstnameUpdated || lastnameUpdated) { + session.save(); + } + + return user; + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } + } + + private boolean updateUserProperty(final User user, final ValueFactory valueFactory, final String property, final String string) throws RepositoryException { + final Value[] values = user.getProperty(property); + if (values != null && values.length > 0) { + if (string.equals(values[0].getString())) { + return false; + } + } + final Value value = valueFactory.createValue(string); + user.setProperty(property, value); + return true; + } + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthAuthenticationHandler.java b/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthAuthenticationHandler.java new file mode 100644 index 0000000..79f58d0 --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthAuthenticationHandler.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth.impl; + +import java.io.IOException; +import java.util.Dictionary; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.apache.commons.lang.StringUtils; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.PropertyUnbounded; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.auth.core.spi.AuthenticationHandler; +import org.apache.sling.auth.core.spi.AuthenticationInfo; +import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler; +import org.apache.sling.auth.xing.api.XingUser; +import org.apache.sling.auth.xing.api.users.Users; +import org.apache.sling.auth.xing.oauth.XingOauth; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.osgi.framework.Constants; +import org.osgi.service.component.ComponentContext; +import org.scribe.builder.ServiceBuilder; +import org.scribe.builder.api.XingApi; +import org.scribe.model.OAuthConstants; +import org.scribe.model.OAuthRequest; +import org.scribe.model.Response; +import org.scribe.model.Token; +import org.scribe.model.Verb; +import org.scribe.model.Verifier; +import org.scribe.oauth.OAuthService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + label = "Apache Sling Authentication XING OAuth “Authentication Handler”", + description = "Authentication Handler for Sling Authentication XING OAuth", + immediate = true, + metatype = true +) +@Service +@Properties({ + @Property(name = Constants.SERVICE_VENDOR, value = XingOauth.SERVICE_VENDOR), + @Property(name = Constants.SERVICE_DESCRIPTION, value = "Authentication Handler for Sling Authentication XING OAuth"), + @Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = false), + @Property(name = AuthenticationHandler.PATH_PROPERTY, value = "/", unbounded = PropertyUnbounded.ARRAY), + @Property(name = AuthenticationHandler.TYPE_PROPERTY, value = XingOauth.AUTH_TYPE, propertyPrivate = true) +}) +public class XingOauthAuthenticationHandler extends DefaultAuthenticationFeedbackHandler implements AuthenticationHandler { + + private OAuthService oAuthService; + + private String consumerKey; + + private String consumerSecret; + + private String callbackUrl; + + private String usersMeUrl; + + private static final String DEFAULT_USERS_ME_URL = "https://api.xing.com/v1/users/me.json"; + + @Property(value = "") + private static final String CONSUMER_KEY_PARAMETER = "org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.consumerKey"; + + @Property(value = "") + private static final String CONSUMER_SECRET_PARAMETER = "org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.consumerSecret"; + + @Property(value = "") + private static final String CALLBACK_URL_PARAMETER = "org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.callbackUrl"; + + @Property(value = DEFAULT_USERS_ME_URL) + private static final String USERS_ME_URL_PARAMETER = "org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.usersMeUrl"; + + public static final String USER_SESSION_ATTRIBUTE_NAME = "xing-user"; + + private final Logger logger = LoggerFactory.getLogger(XingOauthAuthenticationHandler.class); + + public XingOauthAuthenticationHandler() { + } + + @Activate + protected void activate(final ComponentContext componentContext) { + logger.debug("activate"); + configure(componentContext); + } + + @Modified + protected void modified(final ComponentContext componentContext) { + logger.debug("modified"); + configure(componentContext); + } + + @Deactivate + protected void deactivate(final ComponentContext componentContext) { + logger.debug("deactivate"); + } + + protected synchronized void configure(final ComponentContext componentContext) { + final Dictionary properties = componentContext.getProperties(); + consumerKey = PropertiesUtil.toString(properties.get(CONSUMER_KEY_PARAMETER), "").trim(); + consumerSecret = PropertiesUtil.toString(properties.get(CONSUMER_SECRET_PARAMETER), "").trim(); + callbackUrl = PropertiesUtil.toString(properties.get(CALLBACK_URL_PARAMETER), "").trim(); + usersMeUrl = PropertiesUtil.toString(properties.get(USERS_ME_URL_PARAMETER), DEFAULT_USERS_ME_URL).trim(); + + if (StringUtils.isEmpty(consumerKey)) { + logger.warn("configured consumer key is empty"); + } + + if (StringUtils.isEmpty(consumerSecret)) { + logger.warn("configured consumer secret is empty"); + } + + if (StringUtils.isEmpty(callbackUrl)) { + logger.warn("configured callback URL is empty"); + } + + if (StringUtils.isEmpty(usersMeUrl)) { + logger.warn("configured users me URL is empty"); + } + + if (!StringUtils.isEmpty(consumerKey) && !StringUtils.isEmpty(consumerSecret) && !StringUtils.isEmpty(callbackUrl)) { + oAuthService = new ServiceBuilder().provider(XingApi.class).apiKey(consumerKey).apiSecret(consumerSecret).callback(callbackUrl).build(); + } + + logger.info("configured with consumer key '{}', callback url '{}' and users me url '{}'", consumerKey, callbackUrl, usersMeUrl); + } + + // we need the OAuth access token and the user from XING (/v1/users/me) + @Override + public AuthenticationInfo extractCredentials(final HttpServletRequest request, final HttpServletResponse response) { + logger.debug("extract credentials"); + + try { + final HttpSession httpSession = request.getSession(true); + + Token accessToken = (Token) httpSession.getAttribute(OAuthConstants.ACCESS_TOKEN); + XingUser xingUser = (XingUser) httpSession.getAttribute(USER_SESSION_ATTRIBUTE_NAME); + + if (accessToken == null) { + // we need the request token and verifier to get an access token + final Token requestToken = (Token) httpSession.getAttribute(OAuthConstants.TOKEN); + final String verifier = request.getParameter(OAuthConstants.VERIFIER); + if (requestToken == null || verifier == null) { + return null; + } + accessToken = oAuthService.getAccessToken(requestToken, new Verifier(verifier)); + logger.debug("access token: {}", accessToken); + httpSession.setAttribute(OAuthConstants.ACCESS_TOKEN, accessToken); + } + + if (xingUser == null) { + xingUser = fetchUser(accessToken); + logger.debug("xing user: {}", xingUser); + httpSession.setAttribute(USER_SESSION_ATTRIBUTE_NAME, xingUser); + } + + final AuthenticationInfo authenticationInfo = new AuthenticationInfo(XingOauth.AUTH_TYPE, xingUser.getId()); + authenticationInfo.put(XingOauth.AUTHENTICATION_CREDENTIALS_ACCESS_TOKEN_KEY, accessToken); + authenticationInfo.put(XingOauth.AUTHENTICATION_CREDENTIALS_USER_KEY, xingUser); + return authenticationInfo; + } catch (Exception e) { + logger.error(e.getMessage(), e); + removeAuthFromSession(request); + return null; + } + } + + @Override + public boolean requestCredentials(final HttpServletRequest request, final HttpServletResponse response) throws IOException { + logger.debug("request credentials"); + try { + final Token requestToken = oAuthService.getRequestToken(); + logger.debug("received request token: '{}'", requestToken); + final HttpSession httpSession = request.getSession(true); + httpSession.setAttribute(OAuthConstants.TOKEN, requestToken); + final String authUrl = oAuthService.getAuthorizationUrl(requestToken); + logger.debug("redirecting to auth url: '{}'", authUrl); + response.sendRedirect(authUrl); + return true; + } catch (Exception e) { + logger.error(e.getMessage(), e); + return false; + } + } + + @Override + public void dropCredentials(final HttpServletRequest request, final HttpServletResponse response) throws IOException { + logger.debug("drop credentials"); + removeAuthFromSession(request); + } + + protected XingUser fetchUser(final Token accessToken) throws Exception { + final OAuthRequest request = new OAuthRequest(Verb.GET, usersMeUrl); + oAuthService.signRequest(accessToken, request); + final Response response = request.send(); + final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + final Users users = gson.fromJson(response.getBody(), Users.class); + return users.getUsers().get(0); + } + + protected void removeAuthFromSession(final HttpServletRequest request) { + try { + final HttpSession httpSession = request.getSession(); + httpSession.removeAttribute(OAuthConstants.TOKEN); + httpSession.removeAttribute(OAuthConstants.ACCESS_TOKEN); + httpSession.removeAttribute(USER_SESSION_ATTRIBUTE_NAME); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthAuthenticationPlugin.java b/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthAuthenticationPlugin.java new file mode 100644 index 0000000..6be5d90 --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthAuthenticationPlugin.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth.impl; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.api.security.user.User; +import org.apache.sling.auth.xing.api.XingUser; +import org.apache.sling.auth.xing.oauth.XingOauthUserManager; +import org.apache.sling.auth.xing.oauth.XingOauthUtil; +import org.apache.sling.jcr.jackrabbit.server.security.AuthenticationPlugin; +import org.scribe.model.Token; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class XingOauthAuthenticationPlugin implements AuthenticationPlugin { + + final XingOauthUserManager xingOauthUserManager; + + private final Logger logger = LoggerFactory.getLogger(XingOauthAuthenticationPlugin.class); + + public XingOauthAuthenticationPlugin(final XingOauthUserManager xingOauthUserManager) { + this.xingOauthUserManager = xingOauthUserManager; + } + + @Override + public boolean authenticate(final Credentials credentials) throws RepositoryException { + logger.debug("authenticate"); + + final Token accessToken = XingOauthUtil.getAccessToken(credentials); + final XingUser xingUser = XingOauthUtil.getXingUser(credentials); + if (accessToken == null || xingUser == null) { + return false; + } + + User user = xingOauthUserManager.getUser(credentials); + if (user == null) { // check if given credentials pulled up an existing user + logger.debug("no user found for given credentials"); + if (xingOauthUserManager.autoCreate()) { + logger.debug("creating a new user from given user data"); + user = xingOauthUserManager.createUser(credentials); + } + } else { + if (xingOauthUserManager.autoUpdate()) { + xingOauthUserManager.updateUser(credentials); + } + } + + return user != null; + } + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthLoginModulePlugin.java b/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthLoginModulePlugin.java new file mode 100644 index 0000000..a52b494 --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/impl/XingOauthLoginModulePlugin.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.sling.auth.xing.oauth.impl; + +import java.security.Principal; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.sling.auth.xing.api.XingUser; +import org.apache.sling.auth.xing.oauth.XingOauth; +import org.apache.sling.auth.xing.oauth.XingOauthUserManager; +import org.apache.sling.auth.xing.oauth.XingOauthUtil; +import org.apache.sling.jcr.jackrabbit.server.security.AuthenticationPlugin; +import org.apache.sling.jcr.jackrabbit.server.security.LoginModulePlugin; +import org.osgi.framework.Constants; +import org.scribe.model.Token; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + label = "Apache Sling Authentication XING OAuth “Login Module Plugin”", + description = "Login Module Plugin for Sling Authentication XING OAuth", + immediate = true, + metatype = true +) +@Service +@Properties({ + @Property(name = Constants.SERVICE_VENDOR, value = XingOauth.SERVICE_VENDOR), + @Property(name = Constants.SERVICE_DESCRIPTION, value = "Login Module Plugin for Sling Authentication XING OAuth"), + @Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = false) +}) +/** + * @see org.apache.jackrabbit.core.security.authentication.DefaultLoginModule + */ +public class XingOauthLoginModulePlugin implements LoginModulePlugin { + + @Reference + private XingOauthUserManager xingOauthUserManager; + + private final Logger logger = LoggerFactory.getLogger(XingOauthLoginModulePlugin.class); + + public XingOauthLoginModulePlugin() { + } + + @Override + public boolean canHandle(final Credentials credentials) { + logger.debug("canHandle({})", credentials); + final Token accessToken = XingOauthUtil.getAccessToken(credentials); + final XingUser xingUser = XingOauthUtil.getXingUser(credentials); + logger.debug("access token: {}, xing user: {}", accessToken, xingUser); + return accessToken != null && xingUser != null; + } + + @Override + public void doInit(final CallbackHandler callbackHandler, final Session session, final Map map) throws LoginException { + logger.debug("doInit({}, {}, {})", callbackHandler, session, map); + } + + @Override + public Principal getPrincipal(final Credentials credentials) { + logger.debug("getPrincipal({})", credentials); + try { + User user = xingOauthUserManager.getUser(credentials); + if (user == null && xingOauthUserManager.autoCreate()) { + user = xingOauthUserManager.createUser(credentials); + } + if (user != null) { + return user.getPrincipal(); + } + } catch (RepositoryException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + @Override + public void addPrincipals(final Set set) { + logger.debug("addPrincipals({})", set); + } + + @Override + public AuthenticationPlugin getAuthentication(final Principal principal, final Credentials credentials) throws RepositoryException { + logger.debug("getAuthentication({}, {})", principal, credentials); + return new XingOauthAuthenticationPlugin(xingOauthUserManager); + } + + @Override + public int impersonate(final Principal principal, final Credentials credentials) throws RepositoryException, FailedLoginException { + logger.debug("impersonate({}, {})", principal, credentials); + return LoginModulePlugin.IMPERSONATION_DEFAULT; + } + +} diff --git a/src/main/java/org/apache/sling/auth/xing/oauth/package-info.java b/src/main/java/org/apache/sling/auth/xing/oauth/package-info.java new file mode 100644 index 0000000..747ee1e --- /dev/null +++ b/src/main/java/org/apache/sling/auth/xing/oauth/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ [email protected]("0.0.1") +package org.apache.sling.auth.xing.oauth; diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties new file mode 100644 index 0000000..da67f6e --- /dev/null +++ b/src/main/resources/OSGI-INF/metatype/metatype.properties @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +path.name = path +path.description = the path or paths this service is in charge for + +service.ranking.name = service ranking +service.ranking.description = service property for identifying the service's ranking number + +# Authentication Handler +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.consumerKey.name = consumer key +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.consumerKey.description = consumer key from XING + +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.consumerSecret.name = consumer secret +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.consumerSecret.description = consumer secret from XING + +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.callbackUrl.name = callback URL +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.callbackUrl.description = URL to redirect the user-agent after authorization has been granted + +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.usersMeUrl.name = user's profile URL +org.apache.sling.auth.xing.oauth.impl.XingOauthAuthenticationHandler.usersMeUrl.description = URL of the web service to get the user's profile + +# User Manager +org.apache.sling.auth.xing.oauth.impl.DefaultXingOauthUserManager.user.create.auto.name = create user automatically +org.apache.sling.auth.xing.oauth.impl.DefaultXingOauthUserManager.user.create.auto.description = create a new JCR user automatically with given user data from XING + +org.apache.sling.auth.xing.oauth.impl.DefaultXingOauthUserManager.user.update.auto.name = update user automatically +org.apache.sling.auth.xing.oauth.impl.DefaultXingOauthUserManager.user.update.auto.description = update an existing JCR user automatically with given user data from XING -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
