/* * 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(); } else { oAuthService = null; } 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"); if (oAuthService == null) { logger.error("OAuthService is null, check configuration"); return null; } 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"); if (oAuthService == null) { logger.error("OAuthService is null, check configuration"); return false; } 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); } } }