Merge branch 'stable-2.10' into stable-2.11
* stable-2.10: Update 2.10.3 release notes Improve the version computation for the release notes Check reachability from R_HEADS/R_TAGS/REFS_CONFIG when creating branches Update 2.10.3 release notes Include submitter in ChangeMessage on submission Support hybrid OpenID and OAuth2 authentication Release notes for Gerrit 2.10.3 Change-Id: I03e0cbb444e9ae2090f510d6f4e49c6db5ece033
This commit is contained in:
@@ -16,7 +16,6 @@ ASCIIDOC ?= asciidoc
|
||||
ASCIIDOC_EXTRA ?=
|
||||
|
||||
DOC_HTML := $(patsubst %.txt,%.html,$(wildcard ReleaseNotes*.txt))
|
||||
COMMIT := $(shell git describe HEAD | sed s/^v//)
|
||||
|
||||
all: html
|
||||
|
||||
@@ -38,9 +37,8 @@ $(DOC_HTML): %.html : %.txt
|
||||
@echo FORMAT $@
|
||||
@rm -f $@+ $@
|
||||
@v=$$(echo $< | sed 's/^ReleaseNotes-//;s/.txt$$//;') && \
|
||||
c=$$(git rev-list -1 HEAD -- $<) && \
|
||||
n=$$(git describe $$c) && \
|
||||
if [ "X$$n" != "Xv$$v" ]; then v="$$v (from $$n)"; fi && \
|
||||
n=$$(git describe HEAD) && \
|
||||
if ! git diff-index --quiet v$$v -- $< 2>/dev/null; then v="$$v (from $$n)"; fi && \
|
||||
$(ASCIIDOC) --unsafe \
|
||||
-a toc \
|
||||
-a "revision=$$v" \
|
||||
|
124
ReleaseNotes/ReleaseNotes-2.10.3.txt
Normal file
124
ReleaseNotes/ReleaseNotes-2.10.3.txt
Normal file
@@ -0,0 +1,124 @@
|
||||
Release notes for Gerrit 2.10.3
|
||||
===============================
|
||||
|
||||
Download:
|
||||
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.war[
|
||||
https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.war]
|
||||
|
||||
Important Notes
|
||||
---------------
|
||||
|
||||
*WARNING:* There are no schema changes from
|
||||
link:ReleaseNotes-2.10.2.html[2.10.2], but Bouncycastle was upgraded to 1.51.
|
||||
It is therefore important to upgrade the site with the `init` program, rather
|
||||
than only copying the .war file over the existing one.
|
||||
|
||||
*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
|
||||
Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
|
||||
libraries should be manually removed from site's `lib` folder to prevent the
|
||||
startup failure described in
|
||||
link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
|
||||
|
||||
It is recommended to run the `init` program in interactive mode. Warnings will
|
||||
be suppressed in batch mode.
|
||||
|
||||
----
|
||||
java -jar gerrit.war init -d site_path
|
||||
----
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
Support hybrid OpenID and OAuth2 authentication
|
||||
+
|
||||
OpenID auth scheme is aware of optional OAuth2 plugin-based authentication.
|
||||
This feature considered to be experimental and hasn't reached full feature set yet.
|
||||
Particularly, linking of user identities accross protocol boundaries and even from
|
||||
one OAuth2 identity to another OAuth2 identity wasn't implemented yet.
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
* Allow to configure
|
||||
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10.3/config-gerrit.html#sshd.rekeyBytesLimit[
|
||||
SSHD rekey parameters].
|
||||
|
||||
SSH
|
||||
---
|
||||
|
||||
* Update SSHD to 0.14.0.
|
||||
+
|
||||
This fixes link:https://issues.apache.org/jira/browse/SSHD-348[SSHD-348] which
|
||||
was causing ssh threads allocated to stream-events clients to get stuck.
|
||||
+
|
||||
Also update SSHD Mina to 2.0.8 and Bouncycastle to 1.51.
|
||||
|
||||
* link:https://code.google.com/p/gerrit/issues/detail?id=2797[Issue 2797]:
|
||||
Add support for ECDSA based public key authentication.
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
* Prevent wrong content type for CSS files.
|
||||
+
|
||||
The mime-util library contains two content type mappings for .css files:
|
||||
`application/x-pointplus` and `text/css`. Unfortunately, using the wrong one
|
||||
will result in most browsers discarding the file as a CSS file. Ensure we only
|
||||
use the correct type for CSS files.
|
||||
|
||||
* link:https://code.google.com/p/gerrit/issues/detail?id=3289[Issue 3289]:
|
||||
Prevent NullPointerException in Gitweb servlet.
|
||||
|
||||
Replication plugin
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Set connection timeout to 120 seconds for SSH remote operations.
|
||||
|
||||
The creation of a missing Git, before starting replication, is a blocking
|
||||
operation. By setting a timeout, we ensure the operation does not get stuck
|
||||
forever, essentially blocking all future remote git creation operations.
|
||||
|
||||
OAuth extension point
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Respect servlet context path in URL for login token
|
||||
+
|
||||
On sites with non empty context path, first redirect was broken and ended up
|
||||
with 404 Not found.
|
||||
|
||||
* Invalidate OAuth session after web_sessions cache expiration
|
||||
+
|
||||
After web session cache expiration there is no way to re-sign-in into Gerrit.
|
||||
|
||||
Daemon
|
||||
~~~~~~
|
||||
|
||||
* Print proper names for tasks in output of `show-queue` command.
|
||||
+
|
||||
Some tasks were not displayed with the proper name.
|
||||
|
||||
Web UI
|
||||
~~~~~~
|
||||
|
||||
* link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]:
|
||||
Remove stripping `#` in login redirect.
|
||||
|
||||
SSH
|
||||
~~~
|
||||
|
||||
* Prevent double authentication for the same public key.
|
||||
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
* Improved performance when creating a new branch on a repository with a large
|
||||
number of changes.
|
||||
|
||||
|
||||
Upgrades
|
||||
--------
|
||||
|
||||
* Update Bouncycastle to 1.51.
|
||||
|
||||
* Update SSHD to 0.14.0.
|
@@ -9,6 +9,7 @@ Version 2.11.x
|
||||
[[2_10]]
|
||||
Version 2.10.x
|
||||
--------------
|
||||
* link:ReleaseNotes-2.10.3.html[2.10.3]
|
||||
* link:ReleaseNotes-2.10.2.html[2.10.2]
|
||||
* link:ReleaseNotes-2.10.1.html[2.10.1]
|
||||
* link:ReleaseNotes-2.10.html[2.10]
|
||||
|
@@ -71,7 +71,8 @@ class UrlModule extends ServletModule {
|
||||
}
|
||||
serve("/cat/*").with(CatServlet.class);
|
||||
|
||||
if (authConfig.getAuthType() != AuthType.OAUTH) {
|
||||
if (authConfig.getAuthType() != AuthType.OAUTH &&
|
||||
authConfig.getAuthType() != AuthType.OPENID) {
|
||||
serve("/logout").with(HttpLogoutServlet.class);
|
||||
serve("/signout").with(HttpLogoutServlet.class);
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ java_library(
|
||||
'//gerrit-server:server',
|
||||
'//lib:guava',
|
||||
'//lib:gwtorm',
|
||||
'//lib/commons:codec',
|
||||
'//lib/guice:guice',
|
||||
'//lib/guice:guice-servlet',
|
||||
'//lib/jgit:jgit',
|
||||
|
@@ -22,11 +22,14 @@ import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.auth.openid.OpenIdUrls;
|
||||
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.Url;
|
||||
import com.google.gerrit.httpd.HtmlDomUtil;
|
||||
import com.google.gerrit.httpd.LoginUrlToken;
|
||||
import com.google.gerrit.httpd.template.SiteHeaderFooter;
|
||||
import com.google.gerrit.reviewdb.client.AuthType;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
@@ -61,10 +64,13 @@ class LoginForm extends HttpServlet {
|
||||
|
||||
private final ImmutableSet<String> suggestProviders;
|
||||
private final Provider<String> urlProvider;
|
||||
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
|
||||
private final OpenIdServiceImpl impl;
|
||||
private final int maxRedirectUrlLength;
|
||||
private final String ssoUrl;
|
||||
private final SiteHeaderFooter header;
|
||||
private final Provider<CurrentUser> currentUserProvider;
|
||||
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
|
||||
|
||||
@Inject
|
||||
LoginForm(
|
||||
@@ -72,13 +78,19 @@ class LoginForm extends HttpServlet {
|
||||
@GerritServerConfig Config config,
|
||||
AuthConfig authConfig,
|
||||
OpenIdServiceImpl impl,
|
||||
SiteHeaderFooter header) {
|
||||
SiteHeaderFooter header,
|
||||
Provider<OAuthSessionOverOpenID> oauthSessionProvider,
|
||||
Provider<CurrentUser> currentUserProvider,
|
||||
DynamicMap<OAuthServiceProvider> oauthServiceProviders) {
|
||||
this.urlProvider = urlProvider;
|
||||
this.impl = impl;
|
||||
this.header = header;
|
||||
this.maxRedirectUrlLength = config.getInt(
|
||||
"openid", "maxRedirectUrlLength",
|
||||
10);
|
||||
this.oauthSessionProvider = oauthSessionProvider;
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
this.oauthServiceProviders = oauthServiceProviders;
|
||||
|
||||
if (urlProvider == null || Strings.isNullOrEmpty(urlProvider.get())) {
|
||||
log.error("gerrit.canonicalWebUrl must be set in gerrit.config");
|
||||
@@ -152,7 +164,23 @@ class LoginForm extends HttpServlet {
|
||||
mode = SignInMode.SIGN_IN;
|
||||
}
|
||||
|
||||
discover(req, res, link, id, remember, token, mode);
|
||||
OAuthServiceProvider oauthProvider = lookupOAuthServiceProvider(id);
|
||||
|
||||
if (oauthProvider == null) {
|
||||
discover(req, res, link, id, remember, token, mode);
|
||||
} else {
|
||||
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
|
||||
if (!currentUserProvider.get().isIdentifiedUser()
|
||||
&& oauthSession.isLoggedIn()) {
|
||||
oauthSession.logout();
|
||||
}
|
||||
if ((isGerritLogin(req)
|
||||
|| oauthSession.isOAuthFinal(req))
|
||||
&& !oauthSession.isLoggedIn()) {
|
||||
oauthSession.setServiceProvider(oauthProvider);
|
||||
oauthSession.login(req, res, oauthProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void discover(HttpServletRequest req, HttpServletResponse res,
|
||||
@@ -267,6 +295,20 @@ class LoginForm extends HttpServlet {
|
||||
}
|
||||
a.setAttribute("href", u.toString());
|
||||
}
|
||||
|
||||
// OAuth: Add plugin based providers
|
||||
Element providers = HtmlDomUtil.find(doc, "providers");
|
||||
Set<String> plugins = oauthServiceProviders.plugins();
|
||||
for (String pluginName : plugins) {
|
||||
Map<String, Provider<OAuthServiceProvider>> m =
|
||||
oauthServiceProviders.byPlugin(pluginName);
|
||||
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
|
||||
: m.entrySet()) {
|
||||
addProvider(providers, pluginName, e.getKey(),
|
||||
e.getValue().get().getName());
|
||||
}
|
||||
}
|
||||
|
||||
sendHtml(res, doc);
|
||||
}
|
||||
|
||||
@@ -285,6 +327,38 @@ class LoginForm extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addProvider(Element form, String pluginName,
|
||||
String id, String serviceName) {
|
||||
Element div = form.getOwnerDocument().createElement("div");
|
||||
div.setAttribute("id", id);
|
||||
Element hyperlink = form.getOwnerDocument().createElement("a");
|
||||
hyperlink.setAttribute("href", String.format("?id=%s_%s",
|
||||
pluginName, id));
|
||||
hyperlink.setTextContent(serviceName +
|
||||
" (" + pluginName + " plugin)");
|
||||
div.appendChild(hyperlink);
|
||||
form.appendChild(div);
|
||||
}
|
||||
|
||||
private OAuthServiceProvider lookupOAuthServiceProvider(String providerId) {
|
||||
if (providerId.startsWith("http://")) {
|
||||
providerId = providerId.substring("http://".length());
|
||||
}
|
||||
Set<String> plugins = oauthServiceProviders.plugins();
|
||||
for (String pluginName : plugins) {
|
||||
Map<String, Provider<OAuthServiceProvider>> m =
|
||||
oauthServiceProviders.byPlugin(pluginName);
|
||||
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
|
||||
: m.entrySet()) {
|
||||
if (providerId.equals(
|
||||
String.format("%s_%s", pluginName, e.getKey()))) {
|
||||
return e.getValue().get();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getLastId(HttpServletRequest req) {
|
||||
Cookie[] cookies = req.getCookies();
|
||||
if (cookies != null) {
|
||||
@@ -296,4 +370,9 @@ class LoginForm extends HttpServlet {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isGerritLogin(HttpServletRequest request) {
|
||||
return request.getRequestURI().indexOf(
|
||||
OAuthSessionOverOpenID.GERRIT_LOGIN) >= 0;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,55 @@
|
||||
// Copyright (C) 2015 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.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.gerrit.audit.AuditService;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.registration.DynamicItem;
|
||||
import com.google.gerrit.httpd.HttpLogoutServlet;
|
||||
import com.google.gerrit.httpd.WebSession;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
class OAuthOverOpenIDLogoutServlet extends HttpLogoutServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Provider<OAuthSessionOverOpenID> oauthSession;
|
||||
|
||||
@Inject
|
||||
OAuthOverOpenIDLogoutServlet(AuthConfig authConfig,
|
||||
DynamicItem<WebSession> webSession,
|
||||
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
|
||||
AuditService audit,
|
||||
Provider<OAuthSessionOverOpenID> oauthSession) {
|
||||
super(authConfig, webSession, urlProvider, audit);
|
||||
this.oauthSession = oauthSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLogout(HttpServletRequest req, HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
super.doLogout(req, rsp);
|
||||
oauthSession.get().logout();
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
// Copyright (C) 2015 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.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
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.extensions.registration.DynamicItem;
|
||||
import com.google.gerrit.extensions.restapi.Url;
|
||||
import com.google.gerrit.httpd.CanonicalWebUrl;
|
||||
import com.google.gerrit.httpd.LoginUrlToken;
|
||||
import com.google.gerrit.httpd.WebSession;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.server.account.AccountException;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.account.AuthResult;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.servlet.SessionScoped;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** OAuth protocol implementation */
|
||||
@SessionScoped
|
||||
class OAuthSessionOverOpenID {
|
||||
static final String GERRIT_LOGIN = "/login";
|
||||
private static final Logger log = LoggerFactory.getLogger(
|
||||
OAuthSessionOverOpenID.class);
|
||||
private static final SecureRandom randomState = newRandomGenerator();
|
||||
private final String state;
|
||||
private final DynamicItem<WebSession> webSession;
|
||||
private final AccountManager accountManager;
|
||||
private final CanonicalWebUrl urlProvider;
|
||||
private OAuthServiceProvider serviceProvider;
|
||||
private OAuthToken token;
|
||||
private OAuthUserInfo user;
|
||||
private String redirectToken;
|
||||
|
||||
@Inject
|
||||
OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
|
||||
AccountManager accountManager,
|
||||
CanonicalWebUrl urlProvider) {
|
||||
this.state = generateRandomState();
|
||||
this.webSession = webSession;
|
||||
this.accountManager = accountManager;
|
||||
this.urlProvider = urlProvider;
|
||||
}
|
||||
|
||||
boolean isLoggedIn() {
|
||||
return token != null && user != null;
|
||||
}
|
||||
|
||||
boolean isOAuthFinal(HttpServletRequest request) {
|
||||
return Strings.emptyToNull(request.getParameter("code")) != null;
|
||||
}
|
||||
|
||||
boolean login(HttpServletRequest request, HttpServletResponse response,
|
||||
OAuthServiceProvider oauth) throws IOException {
|
||||
if (isLoggedIn()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
log.debug("Login " + this);
|
||||
|
||||
if (isOAuthFinal(request)) {
|
||||
if (!checkState(request)) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.debug("Login-Retrieve-User " + this);
|
||||
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
|
||||
|
||||
user = oauth.getUserInfo(token);
|
||||
|
||||
if (isLoggedIn()) {
|
||||
log.debug("Login-SUCCESS " + this);
|
||||
authenticateAndRedirect(request, response);
|
||||
return true;
|
||||
} else {
|
||||
response.sendError(SC_UNAUTHORIZED);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log.debug("Login-PHASE1 " + this);
|
||||
redirectToken = LoginUrlToken.getToken(request);
|
||||
response.sendRedirect(oauth.getAuthorizationUrl() +
|
||||
"&state=" + state);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void authenticateAndRedirect(HttpServletRequest req,
|
||||
HttpServletResponse rsp) throws IOException {
|
||||
com.google.gerrit.server.account.AuthRequest areq =
|
||||
new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
|
||||
AuthResult arsp = null;
|
||||
try {
|
||||
String claimedIdentifier = user.getClaimedIdentity();
|
||||
Account.Id actualId = accountManager.lookup(user.getExternalId());
|
||||
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
|
||||
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
|
||||
if (claimedId != null && actualId != null) {
|
||||
if (claimedId.equals(actualId)) {
|
||||
// Both link to the same account, that's what we expected.
|
||||
} else {
|
||||
// This is (for now) a fatal error. There are two records
|
||||
// for what might be the same user.
|
||||
//
|
||||
log.error("OAuth accounts disagree over user identity:\n"
|
||||
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
|
||||
+ "\n" + " Delgate ID: " + actualId + " is "
|
||||
+ user.getExternalId());
|
||||
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
} else if (claimedId != null && actualId == null) {
|
||||
// Claimed account already exists: link to it.
|
||||
//
|
||||
try {
|
||||
accountManager.link(claimedId, areq);
|
||||
} catch (OrmException e) {
|
||||
log.error("Cannot link: " + user.getExternalId()
|
||||
+ " to user identity:\n"
|
||||
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier);
|
||||
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
areq.setUserName(user.getUserName());
|
||||
areq.setEmailAddress(user.getEmailAddress());
|
||||
areq.setDisplayName(user.getDisplayName());
|
||||
arsp = accountManager.authenticate(areq);
|
||||
} catch (AccountException e) {
|
||||
log.error("Unable to authenticate user \"" + user + "\"", e);
|
||||
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
webSession.get().login(arsp, true);
|
||||
StringBuilder rdr = new StringBuilder(urlProvider.get(req));
|
||||
rdr.append(Url.decode(redirectToken));
|
||||
rsp.sendRedirect(rdr.toString());
|
||||
}
|
||||
|
||||
void logout() {
|
||||
token = null;
|
||||
user = null;
|
||||
redirectToken = null;
|
||||
serviceProvider = null;
|
||||
}
|
||||
|
||||
private boolean checkState(ServletRequest request) {
|
||||
String s = Strings.nullToEmpty(request.getParameter("state"));
|
||||
if (!s.equals(state)) {
|
||||
log.error("Illegal request state '" + s + "' on OAuthProtocol " + this);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SecureRandom newRandomGenerator() {
|
||||
try {
|
||||
return SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"No SecureRandom available for GitHub authentication", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateRandomState() {
|
||||
byte[] state = new byte[32];
|
||||
randomState.nextBytes(state);
|
||||
return Base64.encodeBase64URLSafeString(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OAuthSession [token=" + token + ", user=" + user + "]";
|
||||
}
|
||||
|
||||
public void setServiceProvider(OAuthServiceProvider provider) {
|
||||
this.serviceProvider = provider;
|
||||
}
|
||||
|
||||
public OAuthServiceProvider getServiceProvider() {
|
||||
return serviceProvider;
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
// Copyright (C) 2015 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.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
|
||||
/** OAuth web filter uses active OAuth session to perform OAuth requests */
|
||||
@Singleton
|
||||
class OAuthWebFilterOverOpenID implements Filter {
|
||||
static final String GERRIT_LOGIN = "/login";
|
||||
|
||||
private final Provider<CurrentUser> currentUserProvider;
|
||||
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
|
||||
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
|
||||
private OAuthServiceProvider ssoProvider;
|
||||
|
||||
@Inject
|
||||
OAuthWebFilterOverOpenID(Provider<CurrentUser> currentUserProvider,
|
||||
DynamicMap<OAuthServiceProvider> oauthServiceProviders,
|
||||
Provider<OAuthSessionOverOpenID> oauthSessionProvider) {
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
this.oauthServiceProviders = oauthServiceProviders;
|
||||
this.oauthSessionProvider = oauthSessionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
pickSSOServiceProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
|
||||
if (currentUserProvider.get().isIdentifiedUser()) {
|
||||
if (httpSession != null) {
|
||||
httpSession.invalidate();
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
|
||||
OAuthServiceProvider service = ssoProvider == null
|
||||
? oauthSession.getServiceProvider()
|
||||
: ssoProvider;
|
||||
|
||||
if ((isGerritLogin(httpRequest)
|
||||
|| oauthSession.isOAuthFinal(httpRequest))
|
||||
&& !oauthSession.isLoggedIn()) {
|
||||
if (service == null) {
|
||||
throw new IllegalStateException("service is unknown");
|
||||
}
|
||||
oauthSession.setServiceProvider(service);
|
||||
oauthSession.login(httpRequest, httpResponse, service);
|
||||
} else {
|
||||
chain.doFilter(httpRequest, response);
|
||||
}
|
||||
}
|
||||
|
||||
private void pickSSOServiceProvider() {
|
||||
SortedSet<String> plugins = oauthServiceProviders.plugins();
|
||||
if (plugins.size() == 1) {
|
||||
SortedMap<String, Provider<OAuthServiceProvider>> services =
|
||||
oauthServiceProviders.byPlugin(Iterables.getOnlyElement(plugins));
|
||||
if (services.size() == 1) {
|
||||
ssoProvider = Iterables.getOnlyElement(services.values()).get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isGerritLogin(HttpServletRequest request) {
|
||||
return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
|
||||
}
|
||||
}
|
@@ -14,6 +14,8 @@
|
||||
|
||||
package com.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
/** Servlets related to OpenID authentication. */
|
||||
@@ -21,9 +23,12 @@ public class OpenIdModule extends ServletModule {
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
serve("/login", "/login/*").with(LoginForm.class);
|
||||
serve("/logout").with(OAuthOverOpenIDLogoutServlet.class);
|
||||
filter("/oauth").through(OAuthWebFilterOverOpenID.class);
|
||||
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
|
||||
serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class);
|
||||
filter("/").through(XrdsFilter.class);
|
||||
bind(OpenIdServiceImpl.class);
|
||||
DynamicMap.mapOf(binder(), OAuthServiceProvider.class);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -14,8 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import static org.eclipse.jgit.lib.RefDatabase.ALL;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.common.ChangeHooks;
|
||||
import com.google.gerrit.common.errors.InvalidRevisionException;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
@@ -25,6 +24,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||
@@ -42,6 +42,7 @@ import org.eclipse.jgit.errors.RevisionSyntaxException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefDatabase;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.ObjectWalk;
|
||||
@@ -51,6 +52,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
public class CreateBranch implements RestModifyView<ProjectResource, Input> {
|
||||
private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
|
||||
@@ -220,7 +222,15 @@ public class CreateBranch implements RestModifyView<ProjectResource, Input> {
|
||||
} catch (IncorrectObjectTypeException err) {
|
||||
throw new InvalidRevisionException();
|
||||
}
|
||||
for (final Ref r : repo.getRefDatabase().getRefs(ALL).values()) {
|
||||
RefDatabase refDb = repo.getRefDatabase();
|
||||
Iterable<Ref> refs = Iterables.concat(
|
||||
refDb.getRefs(Constants.R_HEADS).values(),
|
||||
refDb.getRefs(Constants.R_TAGS).values());
|
||||
Ref rc = refDb.getRef(RefNames.REFS_CONFIG);
|
||||
if (rc != null) {
|
||||
refs = Iterables.concat(refs, Collections.singleton(rc));
|
||||
}
|
||||
for (Ref r : refs) {
|
||||
try {
|
||||
rw.markUninteresting(rw.parseAny(r.getObjectId()));
|
||||
} catch (MissingObjectException err) {
|
||||
|
Reference in New Issue
Block a user