Skip to content

Commit 5ed3825

Browse files
committed
Merge changes from oauth branch
Change-Id: If0b55a97ba164358b01cf4a8d3b1fad623e0dcff
2 parents 879bbae + a41d6d6 commit 5ed3825

15 files changed

+580
-7
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/.classpath
2-
/.primary_build_tool
32
/.project
43
/.settings/org.maven.ide.eclipse.prefs
54
/.settings/org.eclipse.m2e.core.prefs

.gitmodules

Whitespace-only changes.

BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ gerrit_plugin(
1515
"@commons_codec//jar:neverlink",
1616
"@scribe//jar",
1717
],
18-
)
18+
)

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ Supported OAuth providers:
99

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

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

2931
```
30-
git clone https://github.com/davido/gerrit-oauth-provider.git
31-
cd gerrit-oauth-provider && bazel build :all
32+
bazel build gerrit-oauth-provider
3233
```
3334

3435
Install

VERSION

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (C) 2017 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.googlesource.gerrit.plugins.oauth;
16+
17+
import com.google.gerrit.extensions.annotations.PluginName;
18+
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
19+
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
20+
import com.google.inject.Inject;
21+
import com.google.inject.Singleton;
22+
import java.io.IOException;
23+
24+
@Singleton
25+
class DisabledOAuthLoginProvider implements OAuthLoginProvider {
26+
private final String pluginName;
27+
28+
@Inject
29+
DisabledOAuthLoginProvider(@PluginName String pluginName) {
30+
this.pluginName = pluginName;
31+
}
32+
33+
@Override
34+
public OAuthUserInfo login(String username, String secret)
35+
throws IOException {
36+
throw new UnsupportedOperationException(
37+
"git over oauth is not implemented by " + pluginName + " plugin");
38+
}
39+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (C) 2017 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.googlesource.gerrit.plugins.oauth;
16+
17+
import com.google.common.base.CharMatcher;
18+
import com.google.gerrit.extensions.annotations.PluginName;
19+
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
20+
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
21+
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
22+
import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
23+
import com.google.gerrit.server.OutputFormat;
24+
import com.google.gerrit.server.config.CanonicalWebUrl;
25+
import com.google.gerrit.server.config.PluginConfig;
26+
import com.google.gerrit.server.config.PluginConfigFactory;
27+
import com.google.gson.JsonElement;
28+
import com.google.gson.JsonObject;
29+
import com.google.inject.Inject;
30+
import com.google.inject.Provider;
31+
import com.google.inject.Singleton;
32+
33+
import org.scribe.builder.ServiceBuilder;
34+
import org.scribe.builder.api.FacebookApi;
35+
import org.scribe.model.OAuthRequest;
36+
import org.scribe.model.Response;
37+
import org.scribe.model.Token;
38+
import org.scribe.model.Verb;
39+
import org.scribe.model.Verifier;
40+
import org.scribe.oauth.OAuthService;
41+
import org.slf4j.Logger;
42+
import org.slf4j.LoggerFactory;
43+
44+
import java.io.IOException;
45+
46+
import javax.servlet.http.HttpServletResponse;
47+
48+
@Singleton
49+
class FacebookOAuthService implements OAuthServiceProvider {
50+
private static final Logger log = LoggerFactory
51+
.getLogger(FacebookOAuthService.class);
52+
static final String CONFIG_SUFFIX = "-facebook-oauth";
53+
private static final String PROTECTED_RESOURCE_URL =
54+
"https://graph.facebook.com/me";
55+
56+
private static final String FACEBOOK_PROVIDER_PREFIX = "facebook-oauth:";
57+
private static final String SCOPE = "email";
58+
private static final String FIELDS_QUERY = "fields";
59+
private static final String FIELDS = "email,name";
60+
private final OAuthService service;
61+
62+
@Inject
63+
FacebookOAuthService(PluginConfigFactory cfgFactory,
64+
@PluginName String pluginName,
65+
@CanonicalWebUrl Provider<String> urlProvider) {
66+
67+
PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName
68+
+ CONFIG_SUFFIX);
69+
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(
70+
urlProvider.get())
71+
+ "/";
72+
73+
service = new ServiceBuilder().provider(FacebookApi.class)
74+
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
75+
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
76+
.callback(canonicalWebUrl + "oauth")
77+
.scope(SCOPE)
78+
.build();
79+
}
80+
81+
@Override
82+
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
83+
OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
84+
Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
85+
request.addQuerystringParameter(FIELDS_QUERY, FIELDS);
86+
service.signRequest(t, request);
87+
Response response = request.send();
88+
89+
if (response.getCode() != HttpServletResponse.SC_OK) {
90+
throw new IOException(String.format("Status %s (%s) for request %s",
91+
response.getCode(), response.getBody(), request.getUrl()));
92+
}
93+
JsonElement userJson = OutputFormat.JSON.newGson().fromJson(
94+
response.getBody(), JsonElement.class);
95+
96+
if (log.isDebugEnabled()) {
97+
log.debug("User info response: {}", response.getBody());
98+
}
99+
if (userJson.isJsonObject()) {
100+
JsonObject jsonObject = userJson.getAsJsonObject();
101+
JsonElement id = jsonObject.get("id");
102+
if (id == null || id.isJsonNull()) {
103+
throw new IOException(
104+
String.format("Response doesn't contain id field"));
105+
}
106+
JsonElement email = jsonObject.get("email");
107+
JsonElement name = jsonObject.get("name");
108+
// Heads up!
109+
// Lets keep `login` equal to `email`, since `username` field is
110+
// deprecated for Facebook API versions v2.0 and higher
111+
JsonElement login = jsonObject.get("email");
112+
113+
return new OAuthUserInfo(FACEBOOK_PROVIDER_PREFIX + id.getAsString(),
114+
login == null || login.isJsonNull() ? null : login.getAsString(),
115+
email == null || email.isJsonNull() ? null : email.getAsString(),
116+
name == null || name.isJsonNull() ? null : name.getAsString(),
117+
null);
118+
}
119+
120+
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object",
121+
userJson));
122+
}
123+
124+
@Override
125+
public OAuthToken getAccessToken(OAuthVerifier rv) {
126+
Verifier vi = new Verifier(rv.getValue());
127+
Token to = service.getAccessToken(null, vi);
128+
OAuthToken result = new OAuthToken(to.getToken(), to.getSecret(),
129+
to.getRawResponse());
130+
131+
return result;
132+
}
133+
134+
@Override
135+
public String getAuthorizationUrl() {
136+
return service.getAuthorizationUrl(null);
137+
}
138+
139+
@Override
140+
public String getVersion() {
141+
return service.getVersion();
142+
}
143+
144+
@Override
145+
public String getName() {
146+
return "Facebook OAuth2";
147+
}
148+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (C) 2017 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.googlesource.gerrit.plugins.oauth;
16+
17+
import org.scribe.builder.api.DefaultApi20;
18+
import org.scribe.exceptions.OAuthException;
19+
import org.scribe.extractors.AccessTokenExtractor;
20+
import org.scribe.model.*;
21+
import org.scribe.oauth.OAuthService;
22+
23+
import org.scribe.utils.Preconditions;
24+
25+
import java.util.regex.Matcher;
26+
import java.util.regex.Pattern;
27+
28+
import static java.lang.String.format;
29+
30+
public class GitLabApi extends DefaultApi20 {
31+
private static final String AUTHORIZE_URL =
32+
"%s/oauth/authorize?client_id=%s&response_type=code&redirect_uri=%s";
33+
34+
private final String rootUrl;
35+
36+
public GitLabApi(String rootUrl) {
37+
this.rootUrl = rootUrl;
38+
}
39+
40+
@Override
41+
public String getAuthorizationUrl(OAuthConfig config) {
42+
return String.format(AUTHORIZE_URL, rootUrl, config.getApiKey(),
43+
config.getCallback());
44+
}
45+
46+
@Override
47+
public String getAccessTokenEndpoint() {
48+
return String.format("%s/oauth/token", rootUrl);
49+
}
50+
51+
@Override
52+
public Verb getAccessTokenVerb() {
53+
return Verb.POST;
54+
}
55+
56+
@Override
57+
public OAuthService createService(OAuthConfig config) {
58+
return new GitLabOAuthService(this, config);
59+
}
60+
61+
@Override
62+
public AccessTokenExtractor getAccessTokenExtractor() {
63+
return new GitLabJsonTokenExtractor();
64+
}
65+
66+
private static final class GitLabOAuthService implements OAuthService {
67+
private static final String VERSION = "2.0";
68+
69+
private static final String GRANT_TYPE = "grant_type";
70+
private static final String GRANT_TYPE_VALUE = "authorization_code";
71+
72+
private final DefaultApi20 api;
73+
private final OAuthConfig config;
74+
75+
/**
76+
* Default constructor
77+
*
78+
* @param api OAuth2.0 api information
79+
* @param config OAuth 2.0 configuration param object
80+
*/
81+
public GitLabOAuthService(DefaultApi20 api, OAuthConfig config) {
82+
this.api = api;
83+
this.config = config;
84+
}
85+
86+
/**
87+
* {@inheritDoc}
88+
*/
89+
@Override
90+
public Token getAccessToken(Token requestToken, Verifier verifier) {
91+
OAuthRequest request =
92+
new OAuthRequest(api.getAccessTokenVerb(),
93+
api.getAccessTokenEndpoint());
94+
request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
95+
request.addBodyParameter(OAuthConstants.CLIENT_SECRET,
96+
config.getApiSecret());
97+
request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
98+
request.addBodyParameter(OAuthConstants.REDIRECT_URI,
99+
config.getCallback());
100+
if (config.hasScope()) {
101+
request.addBodyParameter(OAuthConstants.SCOPE, config.getScope());
102+
}
103+
request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_VALUE);
104+
Response response = request.send();
105+
return api.getAccessTokenExtractor().extract(response.getBody());
106+
}
107+
108+
/**
109+
* {@inheritDoc}
110+
*/
111+
@Override
112+
public Token getRequestToken() {
113+
throw new UnsupportedOperationException(
114+
"Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
115+
}
116+
117+
/**
118+
* {@inheritDoc}
119+
*/
120+
@Override
121+
public String getVersion() {
122+
return VERSION;
123+
}
124+
125+
/**
126+
* {@inheritDoc}
127+
*/
128+
@Override
129+
public void signRequest(Token accessToken, OAuthRequest request) {
130+
request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN,
131+
accessToken.getToken());
132+
}
133+
134+
/**
135+
* {@inheritDoc}
136+
*/
137+
@Override
138+
public String getAuthorizationUrl(Token requestToken) {
139+
return api.getAuthorizationUrl(config);
140+
}
141+
}
142+
143+
private static final class GitLabJsonTokenExtractor implements
144+
AccessTokenExtractor {
145+
private Pattern accessTokenPattern = Pattern
146+
.compile("\"access_token\"\\s*:\\s*\"(\\S*?)\"");
147+
148+
@Override
149+
public Token extract(String response) {
150+
Preconditions.checkEmptyString(response,
151+
"Cannot extract a token from a null or empty String");
152+
Matcher matcher = accessTokenPattern.matcher(response);
153+
if (matcher.find()) {
154+
return new Token(matcher.group(1), "", response);
155+
} else {
156+
throw new OAuthException(
157+
"Cannot extract an acces token. Response was: " + response);
158+
}
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)