Skip to content

Commit

Permalink
Merge changes from oauth branch
Browse files Browse the repository at this point in the history
Change-Id: If0b55a97ba164358b01cf4a8d3b1fad623e0dcff
  • Loading branch information
davido committed Mar 4, 2017
2 parents 879bbae + a41d6d6 commit 5ed3825
Show file tree
Hide file tree
Showing 15 changed files with 580 additions and 7 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/.classpath
/.primary_build_tool
/.project
/.settings/org.maven.ide.eclipse.prefs
/.settings/org.eclipse.m2e.core.prefs
Expand Down
Empty file removed .gitmodules
Empty file.
2 changes: 1 addition & 1 deletion BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ gerrit_plugin(
"@commons_codec//jar:neverlink",
"@scribe//jar",
],
)
)
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Supported OAuth providers:

* [Bitbucket](https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html)
* [CAS](https://www.apereo.org/projects/cas)
* [Facebook](https://developers.facebook.com/docs/facebook-login)
* [GitHub](https://developer.github.com/v3/oauth/)
* [GitLab](https://about.gitlab.com/)
* [Google](https://developers.google.com/identity/protocols/OAuth2)

See the [Wiki](https://github.com/davido/gerrit-oauth-provider/wiki) what it can do for you.
Expand All @@ -27,8 +29,7 @@ The plugin can be bulit with Bazel. To build the plugin install
following:

```
git clone https://github.com/davido/gerrit-oauth-provider.git
cd gerrit-oauth-provider && bazel build :all
bazel build gerrit-oauth-provider
```

Install
Expand Down
1 change: 0 additions & 1 deletion VERSION

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) 2017 The Android Open Source Project
//
// 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 com.googlesource.gerrit.plugins.oauth;

import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;

@Singleton
class DisabledOAuthLoginProvider implements OAuthLoginProvider {
private final String pluginName;

@Inject
DisabledOAuthLoginProvider(@PluginName String pluginName) {
this.pluginName = pluginName;
}

@Override
public OAuthUserInfo login(String username, String secret)
throws IOException {
throw new UnsupportedOperationException(
"git over oauth is not implemented by " + pluginName + " plugin");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (C) 2017 The Android Open Source Project
//
// 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 com.googlesource.gerrit.plugins.oauth;

import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.FacebookApi;
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;

import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

@Singleton
class FacebookOAuthService implements OAuthServiceProvider {
private static final Logger log = LoggerFactory
.getLogger(FacebookOAuthService.class);
static final String CONFIG_SUFFIX = "-facebook-oauth";
private static final String PROTECTED_RESOURCE_URL =
"https://graph.facebook.com/me";

private static final String FACEBOOK_PROVIDER_PREFIX = "facebook-oauth:";
private static final String SCOPE = "email";
private static final String FIELDS_QUERY = "fields";
private static final String FIELDS = "email,name";
private final OAuthService service;

@Inject
FacebookOAuthService(PluginConfigFactory cfgFactory,
@PluginName String pluginName,
@CanonicalWebUrl Provider<String> urlProvider) {

PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName
+ CONFIG_SUFFIX);
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(
urlProvider.get())
+ "/";

service = new ServiceBuilder().provider(FacebookApi.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.scope(SCOPE)
.build();
}

@Override
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
request.addQuerystringParameter(FIELDS_QUERY, FIELDS);
service.signRequest(t, request);
Response response = request.send();

if (response.getCode() != HttpServletResponse.SC_OK) {
throw new IOException(String.format("Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
JsonElement userJson = OutputFormat.JSON.newGson().fromJson(
response.getBody(), JsonElement.class);

if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
if (userJson.isJsonObject()) {
JsonObject jsonObject = userJson.getAsJsonObject();
JsonElement id = jsonObject.get("id");
if (id == null || id.isJsonNull()) {
throw new IOException(
String.format("Response doesn't contain id field"));
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
// Heads up!
// Lets keep `login` equal to `email`, since `username` field is
// deprecated for Facebook API versions v2.0 and higher
JsonElement login = jsonObject.get("email");

return new OAuthUserInfo(FACEBOOK_PROVIDER_PREFIX + id.getAsString(),
login == null || login.isJsonNull() ? null : login.getAsString(),
email == null || email.isJsonNull() ? null : email.getAsString(),
name == null || name.isJsonNull() ? null : name.getAsString(),
null);
}

throw new IOException(String.format("Invalid JSON '%s': not a JSON Object",
userJson));
}

@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
OAuthToken result = new OAuthToken(to.getToken(), to.getSecret(),
to.getRawResponse());

return result;
}

@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
}

@Override
public String getVersion() {
return service.getVersion();
}

@Override
public String getName() {
return "Facebook OAuth2";
}
}
161 changes: 161 additions & 0 deletions src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (C) 2017 The Android Open Source Project
//
// 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 com.googlesource.gerrit.plugins.oauth;

import org.scribe.builder.api.DefaultApi20;
import org.scribe.exceptions.OAuthException;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.*;
import org.scribe.oauth.OAuthService;

import org.scribe.utils.Preconditions;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.String.format;

public class GitLabApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"%s/oauth/authorize?client_id=%s&response_type=code&redirect_uri=%s";

private final String rootUrl;

public GitLabApi(String rootUrl) {
this.rootUrl = rootUrl;
}

@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(AUTHORIZE_URL, rootUrl, config.getApiKey(),
config.getCallback());
}

@Override
public String getAccessTokenEndpoint() {
return String.format("%s/oauth/token", rootUrl);
}

@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}

@Override
public OAuthService createService(OAuthConfig config) {
return new GitLabOAuthService(this, config);
}

@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return new GitLabJsonTokenExtractor();
}

private static final class GitLabOAuthService implements OAuthService {
private static final String VERSION = "2.0";

private static final String GRANT_TYPE = "grant_type";
private static final String GRANT_TYPE_VALUE = "authorization_code";

private final DefaultApi20 api;
private final OAuthConfig config;

/**
* Default constructor
*
* @param api OAuth2.0 api information
* @param config OAuth 2.0 configuration param object
*/
public GitLabOAuthService(DefaultApi20 api, OAuthConfig config) {
this.api = api;
this.config = config;
}

/**
* {@inheritDoc}
*/
@Override
public Token getAccessToken(Token requestToken, Verifier verifier) {
OAuthRequest request =
new OAuthRequest(api.getAccessTokenVerb(),
api.getAccessTokenEndpoint());
request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
request.addBodyParameter(OAuthConstants.CLIENT_SECRET,
config.getApiSecret());
request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
request.addBodyParameter(OAuthConstants.REDIRECT_URI,
config.getCallback());
if (config.hasScope()) {
request.addBodyParameter(OAuthConstants.SCOPE, config.getScope());
}
request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_VALUE);
Response response = request.send();
return api.getAccessTokenExtractor().extract(response.getBody());
}

/**
* {@inheritDoc}
*/
@Override
public Token getRequestToken() {
throw new UnsupportedOperationException(
"Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
}

/**
* {@inheritDoc}
*/
@Override
public String getVersion() {
return VERSION;
}

/**
* {@inheritDoc}
*/
@Override
public void signRequest(Token accessToken, OAuthRequest request) {
request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN,
accessToken.getToken());
}

/**
* {@inheritDoc}
*/
@Override
public String getAuthorizationUrl(Token requestToken) {
return api.getAuthorizationUrl(config);
}
}

private static final class GitLabJsonTokenExtractor implements
AccessTokenExtractor {
private Pattern accessTokenPattern = Pattern
.compile("\"access_token\"\\s*:\\s*\"(\\S*?)\"");

@Override
public Token extract(String response) {
Preconditions.checkEmptyString(response,
"Cannot extract a token from a null or empty String");
Matcher matcher = accessTokenPattern.matcher(response);
if (matcher.find()) {
return new Token(matcher.group(1), "", response);
} else {
throw new OAuthException(
"Cannot extract an acces token. Response was: " + response);
}
}
}
}
Loading

0 comments on commit 5ed3825

Please sign in to comment.