/* * Google Authentication for SonarQube * Copyright (C) 2016-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonarqube.auth.googleoauth; /*- * #%L * Google Authentication for SonarQube * %% * Copyright (C) 2016 SonarSource * %% * 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. * #L% */ import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.model.*; import com.github.scribejava.core.oauth.OAuthService; import org.sonar.api.server.ServerSide; import org.sonar.api.server.authentication.Display; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import static java.lang.String.format; @ServerSide public class GoogleIdentityProvider implements OAuth2IdentityProvider { private static final Logger LOGGER = Loggers.get(GoogleIdentityProvider.class); public static final String REQUIRED_SCOPE = "openid email"; public static final String KEY = "googleoauth"; private static final Token EMPTY_TOKEN = null; private final GoogleSettings settings; private final UserIdentityFactory userIdentityFactory; private final GoogleScribeApi scribeApi; public GoogleIdentityProvider(GoogleSettings settings, UserIdentityFactory userIdentityFactory, GoogleScribeApi scribeApi) { this.settings = settings; this.userIdentityFactory = userIdentityFactory; this.scribeApi = scribeApi; } @Override public String getKey() { return KEY; } @Override public String getName() { return "Google"; } @Override public Display getDisplay() { return Display.builder() // URL of src/main/resources/static/googleoauth.svg at runtime .setIconPath("/static/authgoogleoauth/googleoauth.svg") .setBackgroundColor("#4d90fe") .build(); } @Override public boolean isEnabled() { return settings.isEnabled(); } @Override public boolean allowsUsersToSignUp() { return settings.allowUsersToSignUp(); } @Override public void init(InitContext context) { OAuthService scribe = newScribeBuilder(context) .scope(REQUIRED_SCOPE) .build(); String url = scribe.getAuthorizationUrl(EMPTY_TOKEN); context.redirectTo(url); } @Override public void callback(CallbackContext context) { HttpServletRequest request = context.getRequest(); OAuthService scribe = newScribeBuilder(context).build(); String oAuthVerifier = request.getParameter("code"); Token accessToken = scribe.getAccessToken(EMPTY_TOKEN, new Verifier(oAuthVerifier)); GsonUser gsonUser = requestUser(scribe, accessToken); String redirectTo; if (settings.oauthDomain()==null || (checkValidDomain(settings.oauthDomain(), gsonUser.getEmail()))) { redirectTo = settings.getSonarBaseURL(); String referer_url = request.getHeader("referer"); try { URL urlObj = new URL(referer_url); String returnToValue = null; for( String param : urlObj.getQuery().split("&")) { if( param.startsWith("return_to=")){ System.out.println("Return_to param : " + param); System.out.println("Web context : " + settings.getWebContext()); param = URLDecoder.decode(param,"UTF-8"); returnToValue = param.split("=",2)[1].replace(settings.getWebContext(),""); } } if(returnToValue != null){ redirectTo = redirectTo.concat(returnToValue); } } catch(Exception e) { LOGGER.trace("Exception while parsing return to URL"); } UserIdentity userIdentity = userIdentityFactory.create(gsonUser); context.authenticate(userIdentity); } else { redirectTo = settings.getSonarBaseURL()+"/sessions/unauthorized#"; } try { context.getResponse().sendRedirect(redirectTo); } catch (IOException ioe) { throw MessageException.of("Unable to redirect after OAuth login", ioe); } } private Boolean checkValidDomain(String oAuthDomains, String userEmail) { for (String domain : oAuthDomains.split(",")) { if (userEmail.trim().endsWith("@" + domain.trim())) { return true; } } return false; } private GsonUser requestUser(OAuthService scribe, Token accessToken) { OAuthRequest userRequest = new OAuthRequest(Verb.GET, settings.apiURL() + "oauth2/v1/userinfo", scribe); scribe.signRequest(accessToken, userRequest); Response userResponse = userRequest.send(); if (!userResponse.isSuccessful()) { throw new IllegalStateException(format("Can not get Google user profile. HTTP code: %s, response: %s", userResponse.getCode(), userResponse.getBody())); } String userResponseBody = userResponse.getBody(); LOGGER.trace("User response received : %s", userResponseBody); return GsonUser.parse(userResponseBody); } private ServiceBuilder newScribeBuilder(OAuth2IdentityProvider.OAuth2Context context) { if (!isEnabled()) { throw new IllegalStateException("Google authentication is disabled"); } return new ServiceBuilder() .provider(scribeApi) .apiKey(settings.clientId()) .apiSecret(settings.clientSecret()) .grantType("authorization_code") .callback(context.getCallbackUrl()); } }