Merge branch 'stable-3.0' into stable-3.1 * stable-3.0: Upgrade bazlets to latest stable-3.0 to build with 3.0.9 API Upgrade bazlets to latest stable-2.16 to build with 2.16.19 API Change-Id: Ia335dd0f48b239801a1e039835d2b2492f4574be
diff --git a/README.md b/README.md index 7a156b2..dae7dcb 100644 --- a/README.md +++ b/README.md
@@ -16,6 +16,7 @@ * [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.
diff --git a/WORKSPACE b/WORKSPACE index 4e5f19d..db6cbf0 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -3,7 +3,7 @@ load("//:bazlets.bzl", "load_bazlets") load_bazlets( - commit = "15eae2ee5cd524a204bd62c3d59bfd0ce86916ec", + commit = "cd715d269734e7bb0aa2fc4a01d2d2e279631cc7", #local_path = "/home/<user>/projects/bazlets", )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/HttpModule.java index 4035a15..c28f932 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/oauth/HttpModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/HttpModule.java
@@ -78,6 +78,13 @@ .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)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/InitOAuth.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/InitOAuth.java index 540b65d..8c874e6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/oauth/InitOAuth.java +++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/InitOAuth.java
@@ -44,6 +44,7 @@ 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; @@ -64,6 +65,8 @@ 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 = @@ -107,11 +110,7 @@ ui.yesno( isConfigured(casOAuthProviderSection), "Use CAS OAuth provider for Gerrit login ?"); if (configureCasOAuthProvider && configureOAuth(casOAuthProviderSection)) { - String rootUrl = casOAuthProviderSection.string("CAS Root URL", ROOT_URL, null); - requireNonNull(rootUrl); - if (!URI.create(rootUrl).isAbsolute()) { - throw new ProvisionException("Root URL must be absolute URL"); - } + checkRootUrl(casOAuthProviderSection.string("CAS Root URL", ROOT_URL, null)); casOAuthProviderSection.string(FIX_LEGACY_USER_ID_QUESTION, FIX_LEGACY_USER_ID, "false"); } @@ -128,22 +127,23 @@ isConfigured(gitlabOAuthProviderSection), "Use GitLab OAuth provider for Gerrit login ?"); if (configureGitLabOAuthProvider && configureOAuth(gitlabOAuthProviderSection)) { - String rootUrl = gitlabOAuthProviderSection.string("GitLab Root URL", ROOT_URL, null); - requireNonNull(rootUrl); - if (!URI.create(rootUrl).isAbsolute()) { - throw new ProvisionException("Root URL must be absolute URL"); - } + checkRootUrl(gitlabOAuthProviderSection.string("GitLab Root URL", ROOT_URL, null)); + } + + boolean configureLemonLDAPOAuthProvider = + ui.yesno( + isConfigured(lemonldapOAuthProviderSection), + "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)) { - String rootUrl = dexOAuthProviderSection.string("Dex Root URL", ROOT_URL, null); - requireNonNull(rootUrl); - if (!URI.create(rootUrl).isAbsolute()) { - throw new ProvisionException("Root URL must be absolute URL"); - } + checkRootUrl(dexOAuthProviderSection.string("Dex Root URL", ROOT_URL, null)); } boolean configureKeycloakOAuthProvider = @@ -151,11 +151,7 @@ isConfigured(keycloakOAuthProviderSection), "Use Keycloak OAuth provider for Gerrit login ?"); if (configureKeycloakOAuthProvider && configureOAuth(keycloakOAuthProviderSection)) { - String rootUrl = keycloakOAuthProviderSection.string("Keycloak Root URL", ROOT_URL, null); - requireNonNull(rootUrl); - if (!URI.create(rootUrl).isAbsolute()) { - throw new ProvisionException("Root URL must be absolute URL"); - } + checkRootUrl(keycloakOAuthProviderSection.string("Keycloak Root URL", ROOT_URL, null)); keycloakOAuthProviderSection.string("Keycloak Realm", REALM, null); } @@ -200,6 +196,19 @@ 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 public void postRun() throws Exception {} }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/LemonLDAPApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/LemonLDAPApi.java new file mode 100644 index 0000000..7b66c25 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/LemonLDAPApi.java
@@ -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(); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/LemonLDAPOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/LemonLDAPOAuthService.java new file mode 100644 index 0000000..f1a0dbe --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/LemonLDAPOAuthService.java
@@ -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"; + } +}