gerrit-oauth-provider: bump

This now tracks upstream's master at 296a0051e1692da91a9b0d3c9b878ac571dc9819

Change-Id: Id08e3a43bcabc3bc4f6341dd5973025e53e02e84
This commit is contained in:
q3k 2020-12-17 20:50:29 +01:00
parent 9708ba02ec
commit bfa4a65f76
41 changed files with 1422 additions and 959 deletions

View file

@ -1,3 +1,4 @@
load("@rules_java//java:defs.bzl", "java_library")
load("//devtools/gerrit/gerrit-oauth-provider/tools/bzl:junit.bzl", "junit_tests")
load(
"//devtools/gerrit/gerrit-oauth-provider/tools/bzl:plugin.bzl",
@ -19,7 +20,8 @@ gerrit_plugin(
resources = glob(["src/main/resources/**/*"]),
deps = [
"@commons-codec//jar:neverlink",
"@scribe//jar",
"@jackson-databind//jar",
"@scribejava-core//jar",
],
)
@ -29,6 +31,7 @@ junit_tests(
tags = ["oauth"],
deps = [
":gerrit-oauth-provider__plugin_test_deps",
"@scribejava-core//jar",
],
)
@ -38,6 +41,5 @@ java_library(
visibility = ["//visibility:public"],
exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
":gerrit-oauth-provider__plugin",
"@scribe//jar",
],
)

View file

@ -16,6 +16,7 @@ Supported OAuth providers:
* [GitLab](https://about.gitlab.com/)
* [Google](https://developers.google.com/identity/protocols/OAuth2)
* [Keycloak](http://www.keycloak.org/)
* [LemonLDAP::NG](https://lemonldap-ng.org)
* [Office365](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols)
See the [Wiki](https://github.com/davido/gerrit-oauth-provider/wiki) what it can do for you.
@ -40,7 +41,7 @@ following:
Install
-------
Copy the `bazel-genfiles/oauth.jar` to
Copy the `bazel-bin/oauth.jar` to
`$gerrit_site/plugins` and re-run init to configure it:
```

View file

@ -1,10 +1,24 @@
load("//tools/bzl:maven_jar.bzl", "maven_jar")
load("//devtools/gerrit/gerrit-oauth-provider/tools/bzl:maven_jar.bzl", "maven_jar")
def external_plugin_deps(omit_commons_codec = True):
JACKSON_VERS = "2.10.2"
maven_jar(
name = "scribe",
artifact = "org.scribe:scribe:1.3.7",
sha1 = "583921bed46635d9f529ef5f14f7c9e83367bc6e",
name = "scribejava-core",
artifact = "com.github.scribejava:scribejava-core:6.9.0",
sha1 = "ed761f450d8382f75787e8fee9ae52e7ec768747",
)
maven_jar(
name = "jackson-annotations",
artifact = "com.fasterxml.jackson.core:jackson-annotations:" + JACKSON_VERS,
sha1 = "3a13b6105946541b8d4181a0506355b5fae63260",
)
maven_jar(
name = "jackson-databind",
artifact = "com.fasterxml.jackson.core:jackson-databind:" + JACKSON_VERS,
sha1 = "0528de95f198afafbcfb0c09d2e43b6e0ea663ec",
deps = [
"@jackson-annotations//jar",
],
)
if not omit_commons_codec:
maven_jar(

View file

@ -14,43 +14,23 @@
package com.googlesource.gerrit.plugins.oauth;
import static java.lang.String.format;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
public class AirVantageApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"https://eu.airvantage.net/api/oauth/authorize?client_id=%s&response_type=code";
private static final String ACCESS_TOKEN_ENDPOINT = "https://eu.airvantage.net/api/oauth/token";
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return format(AUTHORIZE_URL, config.getApiKey());
public String getAuthorizationBaseUrl() {
return "https://eu.airvantage.net/api/oauth/authorize";
}
@Override
public String getAccessTokenEndpoint() {
return ACCESS_TOKEN_ENDPOINT;
return "https://eu.airvantage.net/api/oauth/token";
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return new JsonTokenExtractor();
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
}

View file

@ -18,6 +18,12 @@ import static com.google.gerrit.json.OutputFormat.JSON;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.slf4j.LoggerFactory.getLogger;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -33,13 +39,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.scribe.builder.ServiceBuilder;
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 java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
@Singleton
@ -49,7 +49,7 @@ public class AirVantageOAuthService implements OAuthServiceProvider {
private static final String AV_PROVIDER_PREFIX = "airvantage-oauth:";
private static final String PROTECTED_RESOURCE_URL =
"https://eu.airvantage.net/api/v1/users/current";
private final OAuthService service;
private final OAuth20Service service;
@Inject
AirVantageOAuthService(
@ -60,44 +60,47 @@ public class AirVantageOAuthService implements OAuthServiceProvider {
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
service =
new ServiceBuilder()
.provider(AirVantageApi.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.build();
.build(new AirVantageApi());
}
@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());
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
service.signRequest(t, request);
Response response = request.send();
if (response.getCode() != SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
JsonElement userJson = 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("uid");
if (id == null || id.isJsonNull()) {
throw new IOException("Response doesn't contain uid field");
JsonElement userJson = null;
try (Response response = service.execute(request)) {
if (response.getCode() != SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
return new OAuthUserInfo(
AV_PROVIDER_PREFIX + id.getAsString(),
null,
email.getAsString(),
name.getAsString(),
id.getAsString());
userJson = 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("uid");
if (id == null || id.isJsonNull()) {
throw new IOException("Response doesn't contain uid field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
return new OAuthUserInfo(
AV_PROVIDER_PREFIX + id.getAsString(),
null,
email.getAsString(),
name.getAsString(),
id.getAsString());
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@ -105,14 +108,20 @@ public class AirVantageOAuthService implements OAuthServiceProvider {
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,137 +14,23 @@
package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import static java.lang.String.format;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.scribe.model.OAuthConstants.ACCESS_TOKEN;
import static org.scribe.model.OAuthConstants.CODE;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.exceptions.OAuthException;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.OAuthConfig;
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 com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
public class BitbucketApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"https://bitbucket.org/site/oauth2/authorize?client_id=%s&response_type=code";
private static final String ACCESS_TOKEN_ENDPOINT =
"https://bitbucket.org/site/oauth2/access_token";
public BitbucketApi() {}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return format(AUTHORIZE_URL, config.getApiKey());
public String getAuthorizationBaseUrl() {
return "https://bitbucket.org/site/oauth2/authorize";
}
@Override
public String getAccessTokenEndpoint() {
return ACCESS_TOKEN_ENDPOINT;
return "https://bitbucket.org/site/oauth2/access_token";
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}
@Override
public OAuthService createService(OAuthConfig config) {
return new BitbucketOAuthService(this, config);
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return new BitbucketTokenExtractor();
}
private static final class BitbucketOAuthService 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;
private BitbucketOAuthService(DefaultApi20 api, OAuthConfig config) {
this.config = config;
this.api = api;
}
@Override
public Token getAccessToken(Token token, Verifier verifier) {
OAuthRequest request =
new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
request.addHeader("Authorization", prepareAuthorizationHeaderValue());
request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_VALUE);
request.addBodyParameter(CODE, verifier.getValue());
Response response = request.send();
if (response.getCode() == SC_OK) {
Token t = api.getAccessTokenExtractor().extract(response.getBody());
return new Token(t.getToken(), config.getApiSecret());
}
throw new OAuthException(
String.format(
"Error response received: %s, HTTP status: %s",
response.getBody(), response.getCode()));
}
private String prepareAuthorizationHeaderValue() {
String value = String.format("%s:%s", config.getApiKey(), config.getApiSecret());
String valueBase64 = BaseEncoding.base64().encode(value.getBytes());
return String.format("Basic %s", valueBase64);
}
@Override
public Token getRequestToken() {
throw new UnsupportedOperationException(
"Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
}
@Override
public String getVersion() {
return VERSION;
}
@Override
public void signRequest(Token token, OAuthRequest request) {
request.addQuerystringParameter(ACCESS_TOKEN, token.getToken());
}
@Override
public String getAuthorizationUrl(Token token) {
return api.getAuthorizationUrl(config);
}
}
private static final class BitbucketTokenExtractor implements AccessTokenExtractor {
@Override
public Token extract(String response) {
JsonElement json = JSON.newGson().fromJson(response, JsonElement.class);
if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement id = jsonObject.get(ACCESS_TOKEN);
if (id == null || id.isJsonNull()) {
throw new OAuthException("Response doesn't contain 'access_token' field");
}
JsonElement accessToken = jsonObject.get(ACCESS_TOKEN);
return new Token(accessToken.getAsString(), "");
}
throw new OAuthException(String.format("Invalid JSON '%s': not a JSON Object", json));
}
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
}

View file

@ -18,6 +18,12 @@ import static com.google.gerrit.json.OutputFormat.JSON;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.slf4j.LoggerFactory.getLogger;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -33,13 +39,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.scribe.builder.ServiceBuilder;
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 java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
@Singleton
@ -49,7 +49,7 @@ public class BitbucketOAuthService implements OAuthServiceProvider {
private static final String BITBUCKET_PROVIDER_PREFIX = "bitbucket-oauth:";
private static final String PROTECTED_RESOURCE_URL = "https://bitbucket.org/api/1.0/user/";
private final boolean fixLegacyUserId;
private final OAuthService service;
private final OAuth20Service service;
@Inject
BitbucketOAuthService(
@ -61,46 +61,50 @@ public class BitbucketOAuthService implements OAuthServiceProvider {
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
fixLegacyUserId = cfg.getBoolean(InitOAuth.FIX_LEGACY_USER_ID, false);
service =
new ServiceBuilder()
.provider(BitbucketApi.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.build();
.build(new BitbucketApi());
}
@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());
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
service.signRequest(t, request);
Response response = request.send();
if (response.getCode() != SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
if (userJson.isJsonObject()) {
JsonObject jsonObject = userJson.getAsJsonObject();
JsonObject userObject = jsonObject.getAsJsonObject("user");
if (userObject == null || userObject.isJsonNull()) {
throw new IOException("Response doesn't contain 'user' field");
}
JsonElement usernameElement = userObject.get("username");
String username = usernameElement.getAsString();
JsonElement displayName = jsonObject.get("display_name");
return new OAuthUserInfo(
BITBUCKET_PROVIDER_PREFIX + username,
username,
null,
displayName == null || displayName.isJsonNull() ? null : displayName.getAsString(),
fixLegacyUserId ? username : null);
JsonElement userJson = null;
try (Response response = service.execute(request)) {
if (response.getCode() != SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
if (userJson.isJsonObject()) {
JsonObject jsonObject = userJson.getAsJsonObject();
JsonObject userObject = jsonObject.getAsJsonObject("user");
if (userObject == null || userObject.isJsonNull()) {
throw new IOException("Response doesn't contain 'user' field");
}
JsonElement usernameElement = userObject.get("username");
String username = usernameElement.getAsString();
JsonElement displayName = jsonObject.get("display_name");
return new OAuthUserInfo(
BITBUCKET_PROVIDER_PREFIX + username,
username,
null,
displayName == null || displayName.isJsonNull() ? null : displayName.getAsString(),
fixLegacyUserId ? username : null);
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@ -108,14 +112,20 @@ public class BitbucketOAuthService implements OAuthServiceProvider {
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,15 +14,15 @@
package com.googlesource.gerrit.plugins.oauth;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.OAuthEncoder;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
import com.github.scribejava.core.extractors.TokenExtractor;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
public class CasApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"%s/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s";
private static final String AUTHORIZE_URL = "%s/oauth2.0/authorize";
private final String rootUrl;
@ -36,18 +36,17 @@ public class CasApi extends DefaultApi20 {
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(
AUTHORIZE_URL, rootUrl, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));
public String getAuthorizationBaseUrl() {
return String.format(AUTHORIZE_URL, rootUrl);
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
return OAuth2AccessTokenExtractor.instance();
}
}

View file

@ -16,6 +16,12 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -30,16 +36,12 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletResponse;
import org.scribe.builder.ServiceBuilder;
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;
@ -52,7 +54,7 @@ class CasOAuthService implements OAuthServiceProvider {
private final String rootUrl;
private final boolean fixLegacyUserId;
private final OAuthService service;
private final OAuth20Service service;
@Inject
CasOAuthService(
@ -61,83 +63,88 @@ class CasOAuthService implements OAuthServiceProvider {
@CanonicalWebUrl Provider<String> urlProvider) {
PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName + CONFIG_SUFFIX);
rootUrl = cfg.getString(InitOAuth.ROOT_URL);
if (!URI.create(rootUrl).isAbsolute()) {
throw new ProvisionException("Root URL must be absolute URL");
}
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
fixLegacyUserId = cfg.getBoolean(InitOAuth.FIX_LEGACY_USER_ID, false);
service =
new ServiceBuilder()
.provider(new CasApi(rootUrl))
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.build();
.build(new CasApi(rootUrl));
}
@Override
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
final String protectedResourceUrl = String.format(PROTECTED_RESOURCE_URL, rootUrl);
OAuthRequest request = new OAuthRequest(Verb.GET, protectedResourceUrl);
Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
OAuthRequest request =
new OAuthRequest(Verb.GET, String.format(PROTECTED_RESOURCE_URL, rootUrl));
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
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()));
}
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (!userJson.isJsonObject()) {
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
}
JsonObject jsonObject = userJson.getAsJsonObject();
JsonElement id = jsonObject.get("id");
if (id == null || id.isJsonNull()) {
throw new IOException(String.format("CAS response missing id: %s", response.getBody()));
}
JsonElement attrListJson = jsonObject.get("attributes");
if (attrListJson == null) {
throw new IOException(
String.format("CAS response missing attributes: %s", response.getBody()));
}
String email = null, name = null, login = null;
if (attrListJson.isJsonArray()) {
// It is possible for CAS to be configured to not return any attributes (email, name, login),
// in which case,
// CAS returns an empty JSON object "attributes":{}, rather than "null" or an empty JSON array
// "attributes": []
JsonArray attrJson = attrListJson.getAsJsonArray();
for (JsonElement elem : attrJson) {
if (elem == null || !elem.isJsonObject()) {
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", elem));
}
JsonObject obj = elem.getAsJsonObject();
String property = getStringElement(obj, "email");
if (property != null) email = property;
property = getStringElement(obj, "name");
if (property != null) name = property;
property = getStringElement(obj, "login");
if (property != null) login = property;
try (Response response = service.execute(request)) {
if (response.getCode() != HttpServletResponse.SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
}
return new OAuthUserInfo(
CAS_PROVIDER_PREFIX + id.getAsString(),
login,
email,
name,
fixLegacyUserId ? id.getAsString() : null);
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (!userJson.isJsonObject()) {
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
}
JsonObject jsonObject = userJson.getAsJsonObject();
JsonElement id = jsonObject.get("id");
if (id == null || id.isJsonNull()) {
throw new IOException(String.format("CAS response missing id: %s", response.getBody()));
}
JsonElement attrListJson = jsonObject.get("attributes");
if (attrListJson == null) {
throw new IOException(
String.format("CAS response missing attributes: %s", response.getBody()));
}
String email = null, name = null, login = null;
if (attrListJson.isJsonArray()) {
// It is possible for CAS to be configured to not return any attributes (email, name,
// login),
// in which case,
// CAS returns an empty JSON object "attributes":{}, rather than "null" or an empty JSON
// array
// "attributes": []
JsonArray attrJson = attrListJson.getAsJsonArray();
for (JsonElement elem : attrJson) {
if (elem == null || !elem.isJsonObject()) {
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", elem));
}
JsonObject obj = elem.getAsJsonObject();
String property = getStringElement(obj, "email");
if (property != null) email = property;
property = getStringElement(obj, "name");
if (property != null) name = property;
property = getStringElement(obj, "login");
if (property != null) login = property;
}
}
return new OAuthUserInfo(
CAS_PROVIDER_PREFIX + id.getAsString(),
login,
email,
name,
fixLegacyUserId ? id.getAsString() : null);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
}
private String getStringElement(JsonObject o, String name) {
@ -149,14 +156,20 @@ class CasOAuthService implements OAuthServiceProvider {
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,18 +14,13 @@
package com.googlesource.gerrit.plugins.oauth;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.OAuthEncoder;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
public class DexApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"%s/dex/auth?client_id=%s&response_type=code&redirect_uri=%s&scope=%s";
private static final String AUTHORIZE_URL = "%s/dex/auth";
private final String rootUrl;
@ -34,13 +29,8 @@ public class DexApi extends DefaultApi20 {
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(
AUTHORIZE_URL,
rootUrl,
config.getApiKey(),
OAuthEncoder.encode(config.getCallback()),
config.getScope().replaceAll(" ", "+"));
public String getAuthorizationBaseUrl() {
return String.format(AUTHORIZE_URL, rootUrl);
}
@Override
@ -49,17 +39,7 @@ public class DexApi extends DefaultApi20 {
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return new JsonTokenExtractor();
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
}

View file

@ -16,6 +16,9 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.gerrit.extensions.annotations.PluginName;
@ -30,20 +33,22 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import org.apache.commons.codec.binary.Base64;
import org.scribe.builder.ServiceBuilder;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class DexOAuthService implements OAuthServiceProvider {
private static final Logger log = LoggerFactory.getLogger(DexOAuthService.class);
static final String CONFIG_SUFFIX = "-dex-oauth";
private static final String DEX_PROVIDER_PREFIX = "dex-oauth:";
private final OAuthService service;
private final OAuth20Service service;
private final String rootUrl;
private final String domain;
private final String serviceName;
@ -57,17 +62,18 @@ public class DexOAuthService implements OAuthServiceProvider {
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
rootUrl = cfg.getString(InitOAuth.ROOT_URL);
if (!URI.create(rootUrl).isAbsolute()) {
throw new ProvisionException("Root URL must be absolute URL");
}
domain = cfg.getString(InitOAuth.DOMAIN, null);
serviceName = cfg.getString(InitOAuth.SERVICE_NAME, "Dex OAuth2");
service =
new ServiceBuilder()
.provider(new DexApi(rootUrl))
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.scope("openid profile email offline_access")
.defaultScope("openid profile email offline_access")
.callback(canonicalWebUrl + "oauth")
.build();
.build(new DexApi(rootUrl));
}
private String parseJwt(String input) {
@ -115,14 +121,20 @@ public class DexOAuthService implements OAuthServiceProvider {
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,12 +14,23 @@
package com.googlesource.gerrit.plugins.oauth;
import org.scribe.builder.api.FacebookApi;
import org.scribe.extractors.AccessTokenExtractor;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
public class Facebook2Api extends FacebookApi {
public class Facebook2Api extends DefaultApi20 {
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return OAuth2AccessTokenJsonExtractor.instance();
protected String getAuthorizationBaseUrl() {
return "https://www.facebook.com/dialog/oauth";
}
@Override
public String getAccessTokenEndpoint() {
return "https://graph.facebook.com/oauth/access_token";
}
@Override
public ClientAuthentication getClientAuthentication() {
return RequestBodyAuthenticationScheme.instance();
}
}

View file

@ -16,6 +16,12 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -31,14 +37,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletResponse;
import org.scribe.builder.ServiceBuilder;
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;
@ -52,7 +52,7 @@ class FacebookOAuthService implements OAuthServiceProvider {
private static final String SCOPE = "email";
private static final String FIELDS_QUERY = "fields";
private static final String FIELDS = "email,name";
private final OAuthService service;
private final OAuth20Service service;
@Inject
FacebookOAuthService(
@ -64,53 +64,55 @@ class FacebookOAuthService implements OAuthServiceProvider {
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
service =
new ServiceBuilder()
.provider(Facebook2Api.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.scope(SCOPE)
.build();
.defaultScope(SCOPE)
.build(new Facebook2Api());
}
@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);
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
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 = 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("Response doesn't contain id field");
JsonElement userJson = null;
try (Response response = service.execute(request)) {
if (response.getCode() != HttpServletResponse.SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
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");
userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
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);
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("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);
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@ -118,16 +120,20 @@ class FacebookOAuthService implements OAuthServiceProvider {
@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;
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,22 +14,32 @@
package com.googlesource.gerrit.plugins.oauth;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.model.OAuthConfig;
import org.scribe.utils.OAuthEncoder;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
import com.github.scribejava.core.extractors.TokenExtractor;
import com.github.scribejava.core.model.OAuth2AccessToken;
public class GitHub2Api extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s";
private static final String AUTHORIZE_URL = "%slogin/oauth/authorize";
private final String rootUrl;
public GitHub2Api(String rootUrl) {
this.rootUrl = rootUrl;
}
@Override
public String getAccessTokenEndpoint() {
return "https://github.com/login/oauth/access_token";
return String.format("%slogin/oauth/access_token", rootUrl);
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(
AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));
protected String getAuthorizationBaseUrl() {
return String.format(AUTHORIZE_URL, rootUrl);
}
@Override
public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
return OAuth2AccessTokenExtractor.instance();
}
}

View file

@ -16,6 +16,12 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -31,14 +37,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletResponse;
import org.scribe.builder.ServiceBuilder;
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;
@ -47,11 +47,14 @@ class GitHubOAuthService implements OAuthServiceProvider {
private static final Logger log = LoggerFactory.getLogger(GitHubOAuthService.class);
static final String CONFIG_SUFFIX = "-github-oauth";
private static final String GITHUB_PROVIDER_PREFIX = "github-oauth:";
private static final String PROTECTED_RESOURCE_URL = "https://api.github.com/user";
private static final String GITHUB_API_ENDPOINT_URL = "https://api.github.com/";
private static final String GHE_API_ENDPOINT_URL = "%sapi/v3/";
static final String GITHUB_ROOT_URL = "https://github.com/";
private final String rootUrl;
private static final String SCOPE = "user:email";
static final String SCOPE = "user:email";
private final boolean fixLegacyUserId;
private final OAuthService service;
private final OAuth20Service service;
@Inject
GitHubOAuthService(
@ -61,47 +64,64 @@ class GitHubOAuthService implements OAuthServiceProvider {
PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName + CONFIG_SUFFIX);
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
fixLegacyUserId = cfg.getBoolean(InitOAuth.FIX_LEGACY_USER_ID, false);
rootUrl =
CharMatcher.is('/').trimTrailingFrom(cfg.getString(InitOAuth.ROOT_URL, GITHUB_ROOT_URL))
+ "/";
service =
new ServiceBuilder()
.provider(GitHub2Api.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.scope(SCOPE)
.build();
.defaultScope(SCOPE)
.build(new GitHub2Api(rootUrl));
}
private String getApiUrl() {
return GITHUB_ROOT_URL.equals(rootUrl)
? GITHUB_API_ENDPOINT_URL
: String.format(GHE_API_ENDPOINT_URL, rootUrl);
}
private String getProtectedResourceUrl() {
return getApiUrl() + "user";
}
@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());
OAuthRequest request = new OAuthRequest(Verb.GET, getProtectedResourceUrl());
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
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 = 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("Response doesn't contain id field");
JsonElement userJson = null;
try (Response response = service.execute(request)) {
if (response.getCode() != HttpServletResponse.SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
JsonElement login = jsonObject.get("login");
return new OAuthUserInfo(
GITHUB_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(),
fixLegacyUserId ? id.getAsString() : null);
userJson = 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("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
JsonElement login = jsonObject.get("login");
return new OAuthUserInfo(
GITHUB_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(),
fixLegacyUserId ? id.getAsString() : null);
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@ -109,15 +129,20 @@ class GitHubOAuthService implements OAuthServiceProvider {
@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;
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,15 +14,12 @@
package com.googlesource.gerrit.plugins.oauth;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
public class GitLabApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"%s/oauth/authorize?client_id=%s&response_type=code&redirect_uri=%s";
private static final String AUTHORIZE_URL = "%s/oauth/authorize";
private final String rootUrl;
@ -31,8 +28,8 @@ public class GitLabApi extends DefaultApi20 {
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(AUTHORIZE_URL, rootUrl, config.getApiKey(), config.getCallback());
public String getAuthorizationBaseUrl() {
return String.format(AUTHORIZE_URL, rootUrl);
}
@Override
@ -41,17 +38,7 @@ public class GitLabApi extends DefaultApi20 {
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return OAuth2AccessTokenJsonExtractor.instance();
public ClientAuthentication getClientAuthentication() {
return RequestBodyAuthenticationScheme.instance();
}
}

View file

@ -18,6 +18,12 @@ import static com.google.gerrit.json.OutputFormat.JSON;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.slf4j.LoggerFactory.getLogger;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -31,15 +37,11 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import java.io.IOException;
import org.scribe.builder.ServiceBuilder;
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 java.net.URI;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
@Singleton
@ -48,7 +50,7 @@ public class GitLabOAuthService implements OAuthServiceProvider {
static final String CONFIG_SUFFIX = "-gitlab-oauth";
private static final String PROTECTED_RESOURCE_URL = "%s/api/v3/user";
private static final String GITLAB_PROVIDER_PREFIX = "gitlab-oauth:";
private final OAuthService service;
private final OAuth20Service service;
private final String rootUrl;
@Inject
@ -59,59 +61,69 @@ public class GitLabOAuthService implements OAuthServiceProvider {
PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName + CONFIG_SUFFIX);
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
rootUrl = cfg.getString(InitOAuth.ROOT_URL);
if (!URI.create(rootUrl).isAbsolute()) {
throw new ProvisionException("Root URL must be absolute URL");
}
service =
new ServiceBuilder()
.provider(new GitLabApi(rootUrl))
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.build();
.build(new GitLabApi(rootUrl));
}
@Override
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
final String protectedResourceUrl = String.format(PROTECTED_RESOURCE_URL, rootUrl);
OAuthRequest request = new OAuthRequest(Verb.GET, protectedResourceUrl);
Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
OAuthRequest request =
new OAuthRequest(Verb.GET, String.format(PROTECTED_RESOURCE_URL, rootUrl));
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
service.signRequest(t, request);
Response response = request.send();
if (response.getCode() != SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
try (Response response = service.execute(request)) {
if (response.getCode() != SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
JsonObject jsonObject = userJson.getAsJsonObject();
if (jsonObject == null || jsonObject.isJsonNull()) {
throw new IOException("Response doesn't contain 'user' field" + jsonObject);
}
JsonElement id = jsonObject.get("id");
JsonElement username = jsonObject.get("username");
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
return new OAuthUserInfo(
GITLAB_PROVIDER_PREFIX + id.getAsString(),
username == null || username.isJsonNull() ? null : username.getAsString(),
email == null || email.isJsonNull() ? null : email.getAsString(),
name == null || name.isJsonNull() ? null : name.getAsString(),
null);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
JsonObject jsonObject = userJson.getAsJsonObject();
if (jsonObject == null || jsonObject.isJsonNull()) {
throw new IOException("Response doesn't contain 'user' field" + jsonObject);
}
JsonElement id = jsonObject.get("id");
JsonElement username = jsonObject.get("username");
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
return new OAuthUserInfo(
GITLAB_PROVIDER_PREFIX + id.getAsString(),
username == null || username.isJsonNull() ? null : username.getAsString(),
email == null || email.isJsonNull() ? null : email.getAsString(),
name == null || name.isJsonNull() ? null : name.getAsString(),
null);
}
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -14,50 +14,16 @@
package com.googlesource.gerrit.plugins.oauth;
import static org.scribe.utils.OAuthEncoder.encode;
import com.github.scribejava.core.builder.api.DefaultApi20;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.Preconditions;
// Source: https://github.com/FeedTheCoffers/scribe-java-extras
// License: Apache 2
// https://github.com/FeedTheCoffers/scribe-java-extras/blob/master/pom.xml
public class Google2Api extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s&scope=%s";
@Override
public String getAccessTokenEndpoint() {
return "https://accounts.google.com/o/oauth2/token";
return "https://www.googleapis.com/oauth2/v4/token";
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
Preconditions.checkValidUrl(
config.getCallback(), "Must provide a valid url as callback. Google does not support OOB");
Preconditions.checkEmptyString(
config.getScope(), "Must provide a valid value as scope. Google does not support no scope");
return String.format(
AUTHORIZE_URL, config.getApiKey(), encode(config.getCallback()), encode(config.getScope()));
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return OAuth2AccessTokenJsonExtractor.instance();
public String getAuthorizationBaseUrl() {
return "https://accounts.google.com/o/oauth2/auth";
}
}

View file

@ -16,6 +16,12 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
@ -38,15 +44,9 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.scribe.builder.ServiceBuilder;
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;
@ -58,7 +58,7 @@ class GoogleOAuthService implements OAuthServiceProvider {
private static final String PROTECTED_RESOURCE_URL =
"https://www.googleapis.com/oauth2/v2/userinfo";
private static final String SCOPE = "email profile";
private final OAuthService service;
private final OAuth20Service service;
private final String canonicalWebUrl;
private final List<String> domains;
private final boolean useEmailAsUsername;
@ -80,13 +80,11 @@ class GoogleOAuthService implements OAuthServiceProvider {
this.domains = Arrays.asList(cfg.getStringList(InitOAuth.DOMAIN));
this.useEmailAsUsername = cfg.getBoolean(InitOAuth.USE_EMAIL_AS_USERNAME, false);
this.service =
new ServiceBuilder()
.provider(Google2Api.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.scope(SCOPE)
.build();
.defaultScope(SCOPE)
.build(new Google2Api());
if (log.isDebugEnabled()) {
log.debug("OAuth2: canonicalWebUrl={}", canonicalWebUrl);
log.debug("OAuth2: scope={}", SCOPE);
@ -98,54 +96,59 @@ class GoogleOAuthService implements OAuthServiceProvider {
@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());
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
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 = 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("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
String login = null;
if (domains.size() > 0) {
boolean domainMatched = false;
JsonObject jwtToken = retrieveJWTToken(token);
String hdClaim = retrieveHostedDomain(jwtToken);
for (String domain : domains) {
if (domain.equalsIgnoreCase(hdClaim)) {
domainMatched = true;
break;
JsonElement userJson = null;
try (Response response = service.execute(request)) {
if (response.getCode() != HttpServletResponse.SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
userJson = 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("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
String login = null;
if (domains.size() > 0) {
boolean domainMatched = false;
JsonObject jwtToken = retrieveJWTToken(token);
String hdClaim = retrieveHostedDomain(jwtToken);
for (String domain : domains) {
if (domain.equalsIgnoreCase(hdClaim)) {
domainMatched = true;
break;
}
}
if (!domainMatched) {
// TODO(davido): improve error reporting in OAuth extension point
log.error("Error: hosted domain validation failed: {}", Strings.nullToEmpty(hdClaim));
return null;
}
}
if (!domainMatched) {
// TODO(davido): improve error reporting in OAuth extension point
log.error("Error: hosted domain validation failed: {}", Strings.nullToEmpty(hdClaim));
return null;
if (useEmailAsUsername && !email.isJsonNull()) {
login = email.getAsString().split("@")[0];
}
return new OAuthUserInfo(
GOOGLE_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
login /*username*/,
email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
fixLegacyUserId ? id.getAsString() : null /*claimedIdentity*/);
}
if (useEmailAsUsername && !email.isJsonNull()) {
login = email.getAsString().split("@")[0];
}
return new OAuthUserInfo(
GOOGLE_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
login /*username*/,
email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
fixLegacyUserId ? id.getAsString() : null /*claimedIdentity*/);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@ -197,15 +200,20 @@ class GoogleOAuthService implements OAuthServiceProvider {
@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;
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
String url = service.getAuthorizationUrl(null);
String url = service.getAuthorizationUrl();
try {
if (domains.size() == 1) {
url += "&hd=" + URLEncoder.encode(domains.get(0), StandardCharsets.UTF_8.name());

View file

@ -78,6 +78,13 @@ class HttpModule extends ServletModule {
.to(GitLabOAuthService.class);
}
cfg = cfgFactory.getFromGerritConfig(pluginName + LemonLDAPOAuthService.CONFIG_SUFFIX);
if (cfg.getString(InitOAuth.CLIENT_ID) != null) {
bind(OAuthServiceProvider.class)
.annotatedWith(Exports.named(LemonLDAPOAuthService.CONFIG_SUFFIX))
.to(LemonLDAPOAuthService.class);
}
cfg = cfgFactory.getFromGerritConfig(pluginName + DexOAuthService.CONFIG_SUFFIX);
if (cfg.getString(InitOAuth.CLIENT_ID) != null) {
bind(OAuthServiceProvider.class)
@ -99,18 +106,11 @@ class HttpModule extends ServletModule {
.to(Office365OAuthService.class);
}
cfg = cfgFactory.getFromGerritConfig(pluginName + WarsawHackerspaceOAuthService.CONFIG_SUFFIX);
cfg = cfgFactory.getFromGerritConfig(pluginName + AirVantageOAuthService.CONFIG_SUFFIX);
if (cfg.getString(InitOAuth.CLIENT_ID) != null) {
bind(OAuthServiceProvider.class)
.annotatedWith(Exports.named(WarsawHackerspaceOAuthService.CONFIG_SUFFIX))
.to(WarsawHackerspaceOAuthService.class);
}
cfg = cfgFactory.getFromGerritConfig(pluginName + WarsawHackerspaceOAuthService.CONFIG_SUFFIX);
if (cfg.getString(InitOAuth.CLIENT_ID) != null) {
bind(OAuthServiceProvider.class)
.annotatedWith(Exports.named(WarsawHackerspaceOAuthService.CONFIG_SUFFIX))
.to(WarsawHackerspaceOAuthService.class);
.annotatedWith(Exports.named(AirVantageOAuthService.CONFIG_SUFFIX))
.to(AirVantageOAuthService.class);
}
}
}

View file

@ -13,11 +13,16 @@
// limitations under the License.
package com.googlesource.gerrit.plugins.oauth;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import java.net.URI;
class InitOAuth implements InitStep {
static final String PLUGIN_SECTION = "plugin";
@ -39,11 +44,11 @@ class InitOAuth implements InitStep {
private final Section casOAuthProviderSection;
private final Section facebookOAuthProviderSection;
private final Section gitlabOAuthProviderSection;
private final Section lemonldapOAuthProviderSection;
private final Section dexOAuthProviderSection;
private final Section keycloakOAuthProviderSection;
private final Section office365OAuthProviderSection;
private final Section airVantageOAuthProviderSection;
private final Section warsawHackerspaceOAuthProviderSection;
@Inject
InitOAuth(ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
@ -60,6 +65,8 @@ class InitOAuth implements InitStep {
sections.get(PLUGIN_SECTION, pluginName + FacebookOAuthService.CONFIG_SUFFIX);
this.gitlabOAuthProviderSection =
sections.get(PLUGIN_SECTION, pluginName + GitLabOAuthService.CONFIG_SUFFIX);
this.lemonldapOAuthProviderSection =
sections.get(PLUGIN_SECTION, pluginName + LemonLDAPOAuthService.CONFIG_SUFFIX);
this.dexOAuthProviderSection =
sections.get(PLUGIN_SECTION, pluginName + DexOAuthService.CONFIG_SUFFIX);
this.keycloakOAuthProviderSection =
@ -68,8 +75,6 @@ class InitOAuth implements InitStep {
sections.get(PLUGIN_SECTION, pluginName + Office365OAuthService.CONFIG_SUFFIX);
this.airVantageOAuthProviderSection =
sections.get(PLUGIN_SECTION, pluginName + AirVantageOAuthService.CONFIG_SUFFIX);
this.warsawHackerspaceOAuthProviderSection =
sections.get(PLUGIN_SECTION, pluginName + WarsawHackerspaceOAuthService.CONFIG_SUFFIX);
}
@Override
@ -77,83 +82,129 @@ class InitOAuth implements InitStep {
ui.header("OAuth Authentication Provider");
boolean configureGoogleOAuthProvider =
ui.yesno(true, "Use Google OAuth provider for Gerrit login ?");
if (configureGoogleOAuthProvider) {
configureOAuth(googleOAuthProviderSection);
ui.yesno(
isConfigured(googleOAuthProviderSection),
"Use Google OAuth provider for Gerrit login ?");
if (configureGoogleOAuthProvider && configureOAuth(googleOAuthProviderSection)) {
googleOAuthProviderSection.string(FIX_LEGACY_USER_ID_QUESTION, FIX_LEGACY_USER_ID, "false");
}
boolean configueGitHubOAuthProvider =
ui.yesno(true, "Use GitHub OAuth provider for Gerrit login ?");
if (configueGitHubOAuthProvider) {
configureOAuth(githubOAuthProviderSection);
ui.yesno(
isConfigured(githubOAuthProviderSection),
"Use GitHub OAuth provider for Gerrit login ?");
if (configueGitHubOAuthProvider && configureOAuth(githubOAuthProviderSection)) {
githubOAuthProviderSection.string(FIX_LEGACY_USER_ID_QUESTION, FIX_LEGACY_USER_ID, "false");
}
boolean configureBitbucketOAuthProvider =
ui.yesno(true, "Use Bitbucket OAuth provider for Gerrit login ?");
if (configureBitbucketOAuthProvider) {
configureOAuth(bitbucketOAuthProviderSection);
ui.yesno(
isConfigured(bitbucketOAuthProviderSection),
"Use Bitbucket OAuth provider for Gerrit login ?");
if (configureBitbucketOAuthProvider && configureOAuth(bitbucketOAuthProviderSection)) {
bitbucketOAuthProviderSection.string(
FIX_LEGACY_USER_ID_QUESTION, FIX_LEGACY_USER_ID, "false");
}
boolean configureCasOAuthProvider = ui.yesno(true, "Use CAS OAuth provider for Gerrit login ?");
if (configureCasOAuthProvider) {
casOAuthProviderSection.string("CAS Root URL", ROOT_URL, null);
configureOAuth(casOAuthProviderSection);
boolean configureCasOAuthProvider =
ui.yesno(
isConfigured(casOAuthProviderSection), "Use CAS OAuth provider for Gerrit login ?");
if (configureCasOAuthProvider && configureOAuth(casOAuthProviderSection)) {
checkRootUrl(casOAuthProviderSection.string("CAS Root URL", ROOT_URL, null));
casOAuthProviderSection.string(FIX_LEGACY_USER_ID_QUESTION, FIX_LEGACY_USER_ID, "false");
}
boolean configueFacebookOAuthProvider =
ui.yesno(true, "Use Facebook OAuth provider for Gerrit login ?");
ui.yesno(
isConfigured(facebookOAuthProviderSection),
"Use Facebook OAuth provider for Gerrit login ?");
if (configueFacebookOAuthProvider) {
configureOAuth(facebookOAuthProviderSection);
}
boolean configureGitLabOAuthProvider =
ui.yesno(true, "Use GitLab OAuth provider for Gerrit login ?");
if (configureGitLabOAuthProvider) {
gitlabOAuthProviderSection.string("GitLab Root URL", ROOT_URL, null);
configureOAuth(gitlabOAuthProviderSection);
ui.yesno(
isConfigured(gitlabOAuthProviderSection),
"Use GitLab OAuth provider for Gerrit login ?");
if (configureGitLabOAuthProvider && configureOAuth(gitlabOAuthProviderSection)) {
checkRootUrl(gitlabOAuthProviderSection.string("GitLab Root URL", ROOT_URL, null));
}
boolean configureDexOAuthProvider = ui.yesno(true, "Use Dex OAuth provider for Gerrit login ?");
if (configureDexOAuthProvider) {
dexOAuthProviderSection.string("Dex Root URL", ROOT_URL, null);
configureOAuth(dexOAuthProviderSection);
boolean configureLemonLDAPOAuthProvider =
ui.yesno(true, "Use LemonLDAP OAuth provider for Gerrit login ?");
if (configureLemonLDAPOAuthProvider) {
checkRootUrl(lemonldapOAuthProviderSection.string("LemonLDAP Root URL", ROOT_URL, null));
configureOAuth(lemonldapOAuthProviderSection);
}
boolean configureDexOAuthProvider =
ui.yesno(
isConfigured(dexOAuthProviderSection), "Use Dex OAuth provider for Gerrit login ?");
if (configureDexOAuthProvider && configureOAuth(dexOAuthProviderSection)) {
checkRootUrl(dexOAuthProviderSection.string("Dex Root URL", ROOT_URL, null));
}
boolean configureKeycloakOAuthProvider =
ui.yesno(true, "Use Keycloak OAuth provider for Gerrit login ?");
if (configureKeycloakOAuthProvider) {
keycloakOAuthProviderSection.string("Keycloak Root URL", ROOT_URL, null);
ui.yesno(
isConfigured(keycloakOAuthProviderSection),
"Use Keycloak OAuth provider for Gerrit login ?");
if (configureKeycloakOAuthProvider && configureOAuth(keycloakOAuthProviderSection)) {
checkRootUrl(keycloakOAuthProviderSection.string("Keycloak Root URL", ROOT_URL, null));
keycloakOAuthProviderSection.string("Keycloak Realm", REALM, null);
configureOAuth(keycloakOAuthProviderSection);
}
boolean configureOffice365OAuthProvider =
ui.yesno(true, "Use Office365 OAuth provider for Gerrit login ?");
ui.yesno(
isConfigured(office365OAuthProviderSection),
"Use Office365 OAuth provider for Gerrit login ?");
if (configureOffice365OAuthProvider) {
configureOAuth(office365OAuthProviderSection);
}
boolean configureAirVantageOAuthProvider =
ui.yesno(true, "Use AirVantage OAuth provider for Gerrit login ?");
ui.yesno(
isConfigured(airVantageOAuthProviderSection),
"Use AirVantage OAuth provider for Gerrit login ?");
if (configureAirVantageOAuthProvider) {
configureOAuth(airVantageOAuthProviderSection);
}
boolean configureWarsawHackerspaceOAuthProvider =
ui.yesno(true, "Use Warsaw Hackerspace OAuth provider for Gerrit login?");
if (configureWarsawHackerspaceOAuthProvider) {
configureOAuth(warsawHackerspaceOAuthProviderSection);
}
}
private void configureOAuth(Section s) {
s.string("Application client id", CLIENT_ID, null);
s.passwordForKey("Application client secret", CLIENT_SECRET);
/**
* Retrieve client id to check whether or not this provider was already configured.
*
* @param s OAuth provider section
* @return true if client id key is present, false otherwise
*/
private static boolean isConfigured(Section s) {
return !Strings.isNullOrEmpty(s.get(CLIENT_ID));
}
/**
* Configure OAuth provider section
*
* @param s section to configure
* @return true if section is present, false otherwise
*/
private static boolean configureOAuth(Section s) {
if (!Strings.isNullOrEmpty(s.string("Application client id", CLIENT_ID, null))) {
s.passwordForKey("Application client secret", CLIENT_SECRET);
return true;
}
return false;
}
/**
* Check root URL parameter. It must be not null and it must be an absolute URI.
*
* @param rootUrl root URL
* @throws ProvisionException if rootUrl wasn't provided or is not absolute URI.
*/
private static void checkRootUrl(String rootUrl) {
requireNonNull(rootUrl);
if (!URI.create(rootUrl).isAbsolute()) {
throw new ProvisionException("Root URL must be absolute URL");
}
}
@Override

View file

@ -14,18 +14,15 @@
package com.googlesource.gerrit.plugins.oauth;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.OAuthEncoder;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
public class KeycloakApi extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"%s/auth/realms/%s/protocol/openid-connect/auth?client_id=%s&response_type=code&redirect_uri=%s&scope=%s";
private static final String AUTHORIZE_URL = "%s/auth/realms/%s/protocol/openid-connect/auth";
private final String rootUrl;
private final String realm;
@ -36,14 +33,8 @@ public class KeycloakApi extends DefaultApi20 {
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(
AUTHORIZE_URL,
rootUrl,
realm,
config.getApiKey(),
OAuthEncoder.encode(config.getCallback()),
config.getScope().replaceAll(" ", "+"));
public String getAuthorizationBaseUrl() {
return String.format(AUTHORIZE_URL, rootUrl, realm);
}
@Override
@ -52,17 +43,12 @@ public class KeycloakApi extends DefaultApi20 {
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return new JsonTokenExtractor();
public ClientAuthentication getClientAuthentication() {
return RequestBodyAuthenticationScheme.instance();
}
}

View file

@ -16,6 +16,9 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.gerrit.extensions.annotations.PluginName;
@ -30,12 +33,11 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import org.apache.commons.codec.binary.Base64;
import org.scribe.builder.ServiceBuilder;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,7 +47,7 @@ public class KeycloakOAuthService implements OAuthServiceProvider {
static final String CONFIG_SUFFIX = "-keycloak-oauth";
private static final String KEYCLOAK_PROVIDER_PREFIX = "keycloak-oauth:";
private final OAuthService service;
private final OAuth20Service service;
private final String serviceName;
@Inject
@ -57,17 +59,18 @@ public class KeycloakOAuthService implements OAuthServiceProvider {
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
String rootUrl = cfg.getString(InitOAuth.ROOT_URL);
if (!URI.create(rootUrl).isAbsolute()) {
throw new ProvisionException("Root URL must be absolute URL");
}
String realm = cfg.getString(InitOAuth.REALM);
serviceName = cfg.getString(InitOAuth.SERVICE_NAME, "Keycloak OAuth2");
service =
new ServiceBuilder()
.provider(new KeycloakApi(rootUrl, realm))
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.scope("openid")
.callback(canonicalWebUrl + "oauth")
.build();
.defaultScope("openid")
.build(new KeycloakApi(rootUrl, realm));
}
private String parseJwt(String input) {
@ -116,14 +119,20 @@ public class KeycloakOAuthService implements OAuthServiceProvider {
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
Verifier vi = new Verifier(rv.getValue());
Token to = service.getAccessToken(null, vi);
return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl(null);
return service.getAuthorizationUrl();
}
@Override

View file

@ -0,0 +1,55 @@
// Copyright (C) 2020 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.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
public class LemonLDAPApi extends DefaultApi20 {
private static final String AUTHORIZE_URL = "%s/oauth2/authorize";
private final String rootUrl;
public LemonLDAPApi(String rootUrl) {
this.rootUrl = rootUrl;
}
@Override
public String getAuthorizationBaseUrl() {
return String.format(AUTHORIZE_URL, rootUrl);
}
@Override
public String getAccessTokenEndpoint() {
return String.format("%s/oauth2/token", rootUrl);
}
// TODO(davido): Remove this override, if HttpBasicAuthentication
// scheme is supported.
@Override
public ClientAuthentication getClientAuthentication() {
return RequestBodyAuthenticationScheme.instance();
}
// TODO(davido): Remove this override, if BearerSignatureAuthorization
// request header field is supported.
@Override
public BearerSignature getBearerSignature() {
return BearerSignatureURIQueryParameter.instance();
}
}

View file

@ -0,0 +1,134 @@
// Copyright (C) 2020 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 static com.google.gerrit.json.OutputFormat.JSON;
import static org.slf4j.LoggerFactory.getLogger;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
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.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 java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
@Singleton
public class LemonLDAPOAuthService implements OAuthServiceProvider {
private static final Logger log = getLogger(LemonLDAPOAuthService.class);
static final String CONFIG_SUFFIX = "-lemonldap-oauth";
private static final String PROTECTED_RESOURCE_URL = "%s/oauth2/userinfo";
private static final String LEMONLDAP_PROVIDER_PREFIX = "llng-oauth:";
private final OAuth20Service service;
private final String rootUrl;
@Inject
LemonLDAPOAuthService(
PluginConfigFactory cfgFactory,
@PluginName String pluginName,
@CanonicalWebUrl Provider<String> urlProvider) {
PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName + CONFIG_SUFFIX);
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
rootUrl = cfg.getString(InitOAuth.ROOT_URL);
service =
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.defaultScope("openid+email+profile")
.callback(canonicalWebUrl + "oauth")
.build(new LemonLDAPApi(rootUrl));
}
@Override
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
OAuthRequest request =
new OAuthRequest(Verb.GET, String.format(PROTECTED_RESOURCE_URL, rootUrl));
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
service.signRequest(t, request);
try (Response response = service.execute(request)) {
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 = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
if (log.isDebugEnabled()) {
log.debug("User info response: {}", response.getBody());
}
JsonObject jsonObject = userJson.getAsJsonObject();
if (jsonObject == null || jsonObject.isJsonNull()) {
throw new IOException("Response doesn't contain 'user' field" + jsonObject);
}
JsonElement id = jsonObject.get("sub");
JsonElement username = jsonObject.get("username");
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
return new OAuthUserInfo(
LEMONLDAP_PROVIDER_PREFIX + id.getAsString(),
username == null || username.isJsonNull() ? null : username.getAsString(),
email == null || email.isJsonNull() ? null : email.getAsString(),
name == null || name.isJsonNull() ? null : name.getAsString(),
null);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
}
@Override
public OAuthToken getAccessToken(OAuthVerifier rv) {
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
return service.getAuthorizationUrl();
}
@Override
public String getVersion() {
return service.getVersion();
}
@Override
public String getName() {
return "LemonLDAP::NG OAuth2 provider";
}
}

View file

@ -1,93 +0,0 @@
// 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 static org.slf4j.LoggerFactory.getLogger;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.model.OAuthConfig;
import org.scribe.model.OAuthConstants;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.slf4j.Logger;
/** TODO(gildur): remove when updating to newer scribe lib */
final class OAuth20ServiceImpl implements OAuthService {
private static final Logger log = getLogger(OAuth20ServiceImpl.class);
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 OAuth20ServiceImpl(DefaultApi20 api, OAuthConfig config) {
this.api = api;
this.config = config;
}
@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);
if (log.isDebugEnabled()) {
log.debug("Access token request: {}", request);
}
Response response = request.send();
if (log.isDebugEnabled()) {
log.debug("Access token response: {}", response.getBody());
}
return api.getAccessTokenExtractor().extract(response.getBody());
}
@Override
public Token getRequestToken() {
throw new UnsupportedOperationException(
"Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
}
@Override
public String getVersion() {
return VERSION;
}
@Override
public void signRequest(Token accessToken, OAuthRequest request) {
request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken.getToken());
}
@Override
public String getAuthorizationUrl(Token requestToken) {
return api.getAuthorizationUrl(config);
}
}

View file

@ -1,49 +0,0 @@
// Copyright (C) 2018 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 static org.scribe.model.OAuthConstants.ACCESS_TOKEN;
import com.google.common.annotations.VisibleForTesting;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.scribe.exceptions.OAuthException;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.Token;
import org.scribe.utils.Preconditions;
class OAuth2AccessTokenJsonExtractor implements AccessTokenExtractor {
private static final Pattern ACCESS_TOKEN_REGEX_PATTERN =
Pattern.compile("\"" + ACCESS_TOKEN + "\"\\s*:\\s*\"(\\S*?)\"");
private OAuth2AccessTokenJsonExtractor() {}
private static final AccessTokenExtractor INSTANCE = new OAuth2AccessTokenJsonExtractor();
static AccessTokenExtractor instance() {
return INSTANCE;
}
@VisibleForTesting
@Override
public Token extract(String response) {
Preconditions.checkEmptyString(response, "Cannot extract a token from a null or empty String");
Matcher matcher = ACCESS_TOKEN_REGEX_PATTERN.matcher(response);
if (matcher.find()) {
return new Token(matcher.group(1), "", response);
}
throw new OAuthException("Cannot extract an access token. Response was: " + response);
}
}

View file

@ -14,49 +14,23 @@
package com.googlesource.gerrit.plugins.oauth;
import static org.scribe.utils.OAuthEncoder.encode;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import org.scribe.utils.Preconditions;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
public class Office365Api extends DefaultApi20 {
private static final String AUTHORIZE_URL =
"https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=%s";
@Override
public String getAccessTokenEndpoint() {
return "https://login.microsoftonline.com/organizations/oauth2/v2.0/token";
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
Preconditions.checkValidUrl(
config.getCallback(),
"Must provide a valid url as callback. Office365 does not support OOB");
Preconditions.checkEmptyString(
config.getScope(),
"Must provide a valid value as scope. Office365 does not support no scope");
return String.format(
AUTHORIZE_URL, config.getApiKey(), encode(config.getCallback()), encode(config.getScope()));
public String getAuthorizationBaseUrl() {
return "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize";
}
@Override
public Verb getAccessTokenVerb() {
return Verb.POST;
}
@Override
public OAuthService createService(OAuthConfig config) {
return new OAuth20ServiceImpl(this, config);
}
@Override
public AccessTokenExtractor getAccessTokenExtractor() {
return OAuth2AccessTokenJsonExtractor.instance();
public ClientAuthentication getClientAuthentication() {
return RequestBodyAuthenticationScheme.instance();
}
}

View file

@ -16,6 +16,12 @@ package com.googlesource.gerrit.plugins.oauth;
import static com.google.gerrit.json.OutputFormat.JSON;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.google.common.base.CharMatcher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@ -31,14 +37,8 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletResponse;
import org.scribe.builder.ServiceBuilder;
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;
@ -50,7 +50,7 @@ class Office365OAuthService implements OAuthServiceProvider {
private static final String PROTECTED_RESOURCE_URL = "https://graph.microsoft.com/v1.0/me";
private static final String SCOPE =
"openid offline_access https://graph.microsoft.com/user.readbasic.all";
private final OAuthService service;
private final OAuth20Service service;
private final String canonicalWebUrl;
private final boolean useEmailAsUsername;
@ -63,13 +63,11 @@ class Office365OAuthService implements OAuthServiceProvider {
this.canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
this.useEmailAsUsername = cfg.getBoolean(InitOAuth.USE_EMAIL_AS_USERNAME, false);
this.service =
new ServiceBuilder()
.provider(Office365Api.class)
.apiKey(cfg.getString(InitOAuth.CLIENT_ID))
new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
.apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
.callback(canonicalWebUrl + "oauth")
.scope(SCOPE)
.build();
.defaultScope(SCOPE)
.build(new Office365Api());
if (log.isDebugEnabled()) {
log.debug("OAuth2: canonicalWebUrl={}", canonicalWebUrl);
log.debug("OAuth2: scope={}", SCOPE);
@ -80,38 +78,43 @@ class Office365OAuthService implements OAuthServiceProvider {
@Override
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
request.addHeader("Accept", "*/*");
request.addHeader("Authorization", "Bearer " + token.getToken());
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 = 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("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("mail");
JsonElement name = jsonObject.get("displayName");
String login = null;
OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
service.signRequest(t, request);
if (useEmailAsUsername && !email.isJsonNull()) {
login = email.getAsString().split("@")[0];
JsonElement userJson = null;
try (Response response = service.execute(request)) {
if (response.getCode() != HttpServletResponse.SC_OK) {
throw new IOException(
String.format(
"Status %s (%s) for request %s",
response.getCode(), response.getBody(), request.getUrl()));
}
return new OAuthUserInfo(
OFFICE365_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
login /*username*/,
email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
null);
userJson = 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("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("mail");
JsonElement name = jsonObject.get("displayName");
String login = null;
if (useEmailAsUsername && !email.isJsonNull()) {
login = email.getAsString().split("@")[0];
}
return new OAuthUserInfo(
OFFICE365_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
login /*username*/,
email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
null);
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Cannot retrieve user info resource", e);
}
throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@ -119,15 +122,20 @@ class Office365OAuthService implements OAuthServiceProvider {
@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;
try {
OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
return new OAuthToken(
accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
} catch (InterruptedException | ExecutionException | IOException e) {
String msg = "Cannot retrieve access token";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
@Override
public String getAuthorizationUrl() {
String url = service.getAuthorizationUrl(null);
String url = service.getAuthorizationUrl();
return url;
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class AirVantageApiTest {
private AirVantageApi api;
@Before
public void setUp() {
api = new AirVantageApi();
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class BitbucketApiTest {
private BitbucketApi api;
@Before
public void setUp() {
api = new BitbucketApi();
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
import org.junit.Before;
import org.junit.Test;
public class CasApiTest {
private CasApi api;
@Before
public void setUp() {
api = new CasApi("");
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class DexApiTest {
private DexApi api;
@Before
public void setUp() {
api = new DexApi("");
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class Facebook2ApiTest {
private Facebook2Api api;
@Before
public void setUp() {
api = new Facebook2Api();
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
import org.junit.Before;
import org.junit.Test;
public class GitHub2ApiTest {
private GitHub2Api api;
@Before
public void setUp() {
api = new GitHub2Api(GitHubOAuthService.GITHUB_ROOT_URL);
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class GitLabApiTest {
private GitLabApi api;
@Before
public void setUp() {
api = new GitLabApi("");
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,107 @@
// Copyright (C) 2019 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 static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.inject.Provider;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class GithubApiUrlTest {
private static final String PLUGIN_NAME = "gerrit-oauth-provider";
private static final String CANONICAL_URL = "https://localhost";
private static final String TEST_CLIENT_ID = "test_client_id";
@Mock private PluginConfigFactory pluginConfigFactoryMock;
@Mock private Provider<String> urlProviderMock;
private OAuthServiceProvider getGithubOAuthProvider(String rootUrl) {
PluginConfig pluginConfig =
new PluginConfig(PLUGIN_NAME + GitHubOAuthService.CONFIG_SUFFIX, new Config());
if (!Strings.isNullOrEmpty(rootUrl)) {
pluginConfig.setString(InitOAuth.ROOT_URL, rootUrl);
}
pluginConfig.setString(InitOAuth.CLIENT_ID, TEST_CLIENT_ID);
pluginConfig.setString(InitOAuth.CLIENT_SECRET, "secret");
when(pluginConfigFactoryMock.getFromGerritConfig(
PLUGIN_NAME + GitHubOAuthService.CONFIG_SUFFIX))
.thenReturn(pluginConfig);
when(urlProviderMock.get()).thenReturn(CANONICAL_URL);
return new GitHubOAuthService(pluginConfigFactoryMock, PLUGIN_NAME, urlProviderMock);
}
private String getExpectedUrl(String rootUrl) throws Exception {
if (rootUrl == null) {
rootUrl = GitHubOAuthService.GITHUB_ROOT_URL;
}
rootUrl = CharMatcher.is('/').trimTrailingFrom(rootUrl) + "/";
return String.format(
"%slogin/oauth/authorize?response_type=code&client_id=%s&redirect_uri=%s%s&scope=%s",
rootUrl,
TEST_CLIENT_ID,
URLEncoder.encode(CANONICAL_URL, StandardCharsets.UTF_8.name()),
URLEncoder.encode("/oauth", StandardCharsets.UTF_8.name()),
URLEncoder.encode(GitHubOAuthService.SCOPE, StandardCharsets.UTF_8.name()));
}
@Test
public void nullUrlIsLoaded() throws Exception {
String rootUrl = null;
OAuthServiceProvider provider = getGithubOAuthProvider(rootUrl);
assertThat(provider.getAuthorizationUrl()).isEqualTo(getExpectedUrl(rootUrl));
}
@Test
public void githubUrlIsLoaded() throws Exception {
String rootUrl = "https://github.com";
OAuthServiceProvider provider = getGithubOAuthProvider(rootUrl);
assertThat(provider.getAuthorizationUrl()).isEqualTo(getExpectedUrl(rootUrl));
}
@Test
public void githubUrlWithTrailingSlashIsLoaded() throws Exception {
String rootUrl = "https://github.com/";
OAuthServiceProvider provider = getGithubOAuthProvider(rootUrl);
assertThat(provider.getAuthorizationUrl()).isEqualTo(getExpectedUrl(rootUrl));
}
@Test
public void gheUrlIsLoaded() throws Exception {
String rootUrl = "https://git.yourcompany.com";
OAuthServiceProvider provider = getGithubOAuthProvider(rootUrl);
assertThat(provider.getAuthorizationUrl()).isEqualTo(getExpectedUrl(rootUrl));
}
@Test
public void gheUrlWithTrailingSlashIsLoaded() throws Exception {
String rootUrl = "https://git.yourcompany.com/";
OAuthServiceProvider provider = getGithubOAuthProvider(rootUrl);
assertThat(provider.getAuthorizationUrl()).isEqualTo(getExpectedUrl(rootUrl));
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class Google2ApiTest {
private Google2Api api;
@Before
public void setUp() {
api = new Google2Api();
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class KeycloakApiTest {
private KeycloakApi api;
@Before
public void setUp() {
api = new KeycloakApi("", "");
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -0,0 +1,35 @@
// Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import org.junit.Before;
import org.junit.Test;
public class Office365ApiTest {
private Office365Api api;
@Before
public void setUp() {
api = new Office365Api();
}
@Test
public void testAccessTokenExtractor() {
assertThat(api.getAccessTokenExtractor()).isInstanceOf(OAuth2AccessTokenJsonExtractor.class);
}
}

View file

@ -1,4 +1,4 @@
load("//devtools/gerrit/gerrit-oauth-provider/tools/bzl:classpath.bzl", "classpath_collector")
load("//tools/bzl:classpath.bzl", "classpath_collector")
classpath_collector(
name = "main_classpath_collect",