Skip to content

Commit

Permalink
Merge remote-tracking branch 'oauth/master'
Browse files Browse the repository at this point in the history
* oauth/master:
  Update README
  Provide AirVantage OAuth implementation
  Remove unnecessary calls to String.format in IOException ctors
  Migrate `tools/bazel.rc` to `.bazelrc`
  Format all Java files with google-java-format 1.6
  Format build files with buildifier 0.12.0
  Upgrade bazlets to latest stable-2.14 and use released 2.14.10 API
  Build with 2.14.9-SNAPSHOT API and adjust dependency names
  Update bazlets to latest revision on stable-2.14

Change-Id: I9c7ae7a31013b15e8227fd4fe556ff76087449a4
  • Loading branch information
davido committed Sep 14, 2018
2 parents 4c9f6d8 + ec0e9a6 commit d1ade3e
Show file tree
Hide file tree
Showing 22 changed files with 282 additions and 45 deletions.
File renamed without changes.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/bazel-out
/bazel-testlogs
/.idea
*.swp
4 changes: 2 additions & 2 deletions BUILD
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
load("//tools/bzl:junit.bzl", "junit_tests")
load(
"//tools/bzl:plugin.bzl",
"gerrit_plugin",
"PLUGIN_DEPS",
"gerrit_plugin",
)

gerrit_plugin(
Expand All @@ -17,7 +17,7 @@ gerrit_plugin(
],
resources = glob(["src/main/resources/**/*"]),
deps = [
"@commons_codec//jar:neverlink",
"@commons-codec//jar:neverlink",
"@scribe//jar",
],
)
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Gerrit OAuth2 authentication provider
With this plugin Gerrit can use OAuth2 protocol for authentication.
Supported OAuth providers:

* [AirVantage](https://doc.airvantage.net/av/reference/cloud/API/#API-GeneralInformation-Authentication)
* [Bitbucket](https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html)
* [CAS](https://www.apereo.org/projects/cas)
* [CoreOS Dex](https://github.com/coreos/dex)
Expand All @@ -27,19 +28,20 @@ Prebuilt binary artifacts are available on [release page](https://github.com/dav
Build
-----

The plugin can be bulit with Bazel. To build the plugin install
To build the plugin with Bazel, install
[Bazel](https://bazel.build/versions/master/docs/install.html) and run the
following:

```
bazel build gerrit-oauth-provider
git clone https://gerrit.googlesource.com/plugins/oauth gerrit-oauth-provider
cd gerrit-oauth-provider && bazel build gerrit-oauth-provider
```

Install
-------

Copy the `bazel-genfiles/gerrit-oauth-provider.jar` to `$gerit_site/plugins`
and re-run init to configure it:
Copy the `bazel-genfiles/gerrit-oauth-provider.jar` to
`$gerrit_site/plugins` and re-run init to configure it:

```
java -jar gerrit.war init -d <site>
Expand Down
12 changes: 11 additions & 1 deletion WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ workspace(name = "com_github_davido_gerrit_oauth_provider")
load("//:bazlets.bzl", "load_bazlets")

load_bazlets(
commit = "4f3e1b6a4938dd3d390e0badbc9e033ccfaaabc5",
commit = "5e0b58a466804eca41dd99df75bdf8575de0d167",
# local_path = "/home/<user>/projects/bazlets",
)

# Snapshot Plugin API
#load(
# "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl",
# "gerrit_api_maven_local",
#)

# Load snapshot Plugin API
#gerrit_api_maven_local()

# Release Plugin API
load(
"@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
Expand All @@ -16,4 +25,5 @@ load(
gerrit_api()

load(":external_plugin_deps.bzl", "external_plugin_deps")

external_plugin_deps(omit_commons_codec = False)
27 changes: 13 additions & 14 deletions bazlets.bzl
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
NAME = "com_googlesource_gerrit_bazlets"

def load_bazlets(
commit = None,
local_path = None
):
if not local_path:
native.git_repository(
name = NAME,
remote = "https://gerrit.googlesource.com/bazlets",
commit = commit,
)
else:
native.local_repository(
name = NAME,
path = local_path,
)
commit = None,
local_path = None):
if not local_path:
native.git_repository(
name = NAME,
remote = "https://gerrit.googlesource.com/bazlets",
commit = commit,
)
else:
native.local_repository(
name = NAME,
path = local_path,
)
19 changes: 9 additions & 10 deletions external_plugin_deps.bzl
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
load("//tools/bzl:maven_jar.bzl", "maven_jar")

def external_plugin_deps(omit_commons_codec = True):
maven_jar(
name = "scribe",
artifact = "org.scribe:scribe:1.3.7",
sha1 = "583921bed46635d9f529ef5f14f7c9e83367bc6e",
)
if not omit_commons_codec:
maven_jar(
name = "commons_codec",
artifact = "commons-codec:commons-codec:1.4",
sha1 = "4216af16d38465bbab0f3dff8efa14204f7a399a",
name = "scribe",
artifact = "org.scribe:scribe:1.3.7",
sha1 = "583921bed46635d9f529ef5f14f7c9e83367bc6e",
)

if not omit_commons_codec:
maven_jar(
name = "commons-codec",
artifact = "commons-codec:commons-codec:1.4",
sha1 = "4216af16d38465bbab0f3dff8efa14204f7a399a",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 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;

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());
}

@Override
public String getAccessTokenEndpoint() {
return ACCESS_TOKEN_ENDPOINT;
}

@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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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 com.google.gerrit.server.OutputFormat.JSON;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.slf4j.LoggerFactory.getLogger;

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 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;

@Singleton
public class AirVantageOAuthService implements OAuthServiceProvider {
private static final Logger log = getLogger(AirVantageOAuthService.class);
static final String CONFIG_SUFFIX = "-airvantage-oauth";
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;

@Inject
AirVantageOAuthService(
PluginConfigFactory cfgFactory,
@PluginName String pluginName,
@CanonicalWebUrl Provider<String> urlProvider) {
PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName + CONFIG_SUFFIX);
String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";

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

@Override
public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
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 email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
return new OAuthUserInfo(
AV_PROVIDER_PREFIX + id.getAsString(),
null,
email.getAsString(),
name.getAsString(),
id.getAsString());
}

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

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

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

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

@Override
public String getName() {
return "AirVantage OAuth2";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
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": []
// 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
JsonElement emailElement = claimObject.get("email");
JsonElement nameElement = claimObject.get("name");
if (emailElement == null || emailElement.isJsonNull()) {
throw new IOException(String.format("Response doesn't contain email field"));
throw new IOException("Response doesn't contain email field");
}
if (nameElement == null || nameElement.isJsonNull()) {
throw new IOException(String.format("Response doesn't contain name field"));
throw new IOException("Response doesn't contain name field");
}
String email = emailElement.getAsString();
String name = nameElement.getAsString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
JsonObject jsonObject = userJson.getAsJsonObject();
JsonElement id = jsonObject.get("id");
if (id == null || id.isJsonNull()) {
throw new IOException(String.format("Response doesn't contain id field"));
throw new IOException("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
JsonObject jsonObject = userJson.getAsJsonObject();
JsonElement id = jsonObject.get("id");
if (id == null || id.isJsonNull()) {
throw new IOException(String.format("Response doesn't contain id field"));
throw new IOException("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class GoogleOAuthService implements OAuthServiceProvider {
static final String CONFIG_SUFFIX = "-google-oauth";
private static final String GOOGLE_PROVIDER_PREFIX = "google-oauth:";
private static final String PROTECTED_RESOURCE_URL = "https://www.googleapis.com/userinfo/v2/me";
//"https://www.googleapis.com/plus/v1/people/me/openIdConnect";
// "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
private static final String SCOPE = "email profile";
private final OAuthService service;
private final String canonicalWebUrl;
Expand Down Expand Up @@ -115,7 +115,7 @@ public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
JsonObject jsonObject = userJson.getAsJsonObject();
JsonElement id = jsonObject.get("id");
if (id == null || id.isJsonNull()) {
throw new IOException(String.format("Response doesn't contain id field"));
throw new IOException("Response doesn't contain id field");
}
JsonElement email = jsonObject.get("email");
JsonElement name = jsonObject.get("name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,12 @@ protected void configureServlets() {
.annotatedWith(Exports.named(Office365OAuthService.CONFIG_SUFFIX))
.to(Office365OAuthService.class);
}

cfg = cfgFactory.getFromGerritConfig(pluginName + AirVantageOAuthService.CONFIG_SUFFIX);
if (cfg.getString(InitOAuth.CLIENT_ID) != null) {
bind(OAuthServiceProvider.class)
.annotatedWith(Exports.named(AirVantageOAuthService.CONFIG_SUFFIX))
.to(AirVantageOAuthService.class);
}
}
}
Loading

0 comments on commit d1ade3e

Please sign in to comment.