Merge branch 'stable-2.11'
* stable-2.11: Align parent links with parents in CommitBox Allow scrollable cover messages Update Review UI documentation to include new rebase functionality ChangeData: check patch set visibility in change mergeability check Prevent recursive rebase attempts RebaseDialog can now be closed with ESC Make use of true as default for copyAllScoresIfNoChange consistent Set version to 2.10.1 Release notes for Gerrit 2.10.1 Expose extension point for generic OAuth providers Change-Id: I93af48ce7a600dffc9f73afb9cc8131da26b2a59
This commit is contained in:
commit
d3dcb36cde
@ -145,6 +145,16 @@ request is the exact string supplied in the dialog by the user.
|
||||
The configured <<ldap.username,ldap.username>> identity is not used to obtain
|
||||
account information.
|
||||
+
|
||||
* OAUTH
|
||||
+
|
||||
OAuth is a protocol that lets external apps request authorization to private
|
||||
details in a user's account without getting their password. This is
|
||||
preferred over Basic Authentication because tokens can be limited to specific
|
||||
types of data, and can be revoked by users at any time.
|
||||
+
|
||||
Site owners have to register their application before getting started. Note
|
||||
that provider specific plugins must be used with this authentication scheme.
|
||||
+
|
||||
* `DEVELOPMENT_BECOME_ANY_ACCOUNT`
|
||||
+
|
||||
*DO NOT USE*. Only for use in a development environment.
|
||||
|
@ -239,7 +239,11 @@ onto the tip of the destination branch.
|
||||
If the change depends on another open change, it is rebased onto the
|
||||
current patch set of that other change.
|
||||
+
|
||||
The `Rebase` button is only available if the change can be rebased and
|
||||
It is possible to change parent revision of a change. The new parent
|
||||
revision can be another change towards the same target branch, or
|
||||
the tip of the target branch.
|
||||
+
|
||||
The `Rebase` button is only available if
|
||||
the link:access-control.html#category_rebase[Rebase] access right is
|
||||
assigned. Rebasing merge commits is not supported.
|
||||
|
||||
|
42
ReleaseNotes/ReleaseNotes-2.10.1.txt
Normal file
42
ReleaseNotes/ReleaseNotes-2.10.1.txt
Normal file
@ -0,0 +1,42 @@
|
||||
Release notes for Gerrit 2.10.1
|
||||
===============================
|
||||
|
||||
There are no schema changes from link:ReleaseNotes-2.10.html[2.10].
|
||||
|
||||
Download:
|
||||
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.1.war[
|
||||
https://gerrit-releases.storage.googleapis.com/gerrit-2.10.1.war]
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
* link:https://code.google.com/p/gerrit/issues/detail?id=2260[Issue 2260]:
|
||||
LDAP horrendous login time due to recursive lookup.
|
||||
|
||||
* link:https://code.google.com/p/gerrit/issues/detail?id=3210[Issue 3210]:
|
||||
Null Pointer Exception for query command with --comments switch.
|
||||
|
||||
* link:https://code.google.com/p/gerrit/issues/detail?id=3211[Issue 3211]:
|
||||
Intermittent Null Pointer Exception when showing process queue.
|
||||
|
||||
LDAP
|
||||
----
|
||||
|
||||
* Several performance improvements when using LDAP, both in the number of LDAP
|
||||
requests and in the amount of data transfered.
|
||||
|
||||
* Sites using LDAP for authentication but otherwise rely on local Gerrit groups
|
||||
should set the new `ldap.fetchMemberOfEagerly` option to `false`.
|
||||
|
||||
OAuth
|
||||
-----
|
||||
|
||||
* Expose extension point for generic OAuth providers.
|
||||
|
||||
OpenID
|
||||
------
|
||||
|
||||
* Add support for Launchpad on the login form.
|
||||
|
||||
* Remove pre-configured Google OpenID 2.0 provider from the login form, that is
|
||||
going to be shut down on 20, April 2015.
|
@ -9,6 +9,7 @@ Version 2.11.x
|
||||
[[2_10]]
|
||||
Version 2.10.x
|
||||
--------------
|
||||
* link:ReleaseNotes-2.10.1.html[2.10.1]
|
||||
* link:ReleaseNotes-2.10.html[2.10]
|
||||
|
||||
[[2_9]]
|
||||
|
@ -155,6 +155,19 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
assertThat(r1.getPatchSetId().get()).is(3);
|
||||
}
|
||||
|
||||
@Test(expected = ResourceConflictException.class)
|
||||
public void rebaseChangeBaseRecursion() throws Exception {
|
||||
PushOneCommit.Result r1 = createChange();
|
||||
PushOneCommit.Result r2 = createChange();
|
||||
|
||||
RebaseInput ri = new RebaseInput();
|
||||
ri.base = r2.getCommit().name();
|
||||
gApi.changes()
|
||||
.id(r1.getChangeId())
|
||||
.revision(r1.getCommit().name())
|
||||
.rebase(ri);
|
||||
}
|
||||
|
||||
private Set<Account.Id> getReviewers(String changeId) throws Exception {
|
||||
ChangeInfo ci = gApi.changes().id(changeId).get();
|
||||
Set<Account.Id> result = Sets.newHashSet();
|
||||
|
@ -0,0 +1,74 @@
|
||||
// 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.extensions.auth.oauth;
|
||||
|
||||
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/* Contract that OAuth provider must implement */
|
||||
@ExtensionPoint
|
||||
public interface OAuthServiceProvider {
|
||||
|
||||
/**
|
||||
* Retrieve the request token.
|
||||
*
|
||||
* @return request token
|
||||
*/
|
||||
OAuthToken getRequestToken();
|
||||
|
||||
/**
|
||||
* Returns the URL where you should redirect your users to authenticate
|
||||
* your application.
|
||||
*
|
||||
* @param requestToken the request token you need to authorize
|
||||
* @return the URL where you should redirect your users
|
||||
*/
|
||||
String getAuthorizationUrl(OAuthToken requestToken);
|
||||
|
||||
/**
|
||||
* Retrieve the access token
|
||||
*
|
||||
* @param requestToken request token (obtained previously)
|
||||
* @param verifier verifier code
|
||||
* @return access token
|
||||
*/
|
||||
OAuthToken getAccessToken(OAuthToken requestToken, OAuthVerifier verifier);
|
||||
|
||||
/**
|
||||
* After establishing of secure communication channel, this method supossed to
|
||||
* access the protected resoure and retrieve the username.
|
||||
*
|
||||
* @param token
|
||||
* @return OAuth user information
|
||||
* @throws IOException
|
||||
*/
|
||||
OAuthUserInfo getUserInfo(OAuthToken token) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the OAuth version of the service.
|
||||
*
|
||||
* @return oauth version as string
|
||||
*/
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* Returns the name of this service. This name is resented the user to choose
|
||||
* between multiple service providers
|
||||
*
|
||||
* @return name of the service
|
||||
*/
|
||||
String getName();
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// 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.extensions.auth.oauth;
|
||||
|
||||
/* OAuth token */
|
||||
public class OAuthToken {
|
||||
|
||||
private final String token;
|
||||
private final String secret;
|
||||
private final String raw;
|
||||
|
||||
public OAuthToken(String token, String secret, String raw) {
|
||||
this.token = token;
|
||||
this.secret = secret;
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public String getRaw() {
|
||||
return raw;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// 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.extensions.auth.oauth;
|
||||
|
||||
public class OAuthUserInfo {
|
||||
|
||||
private final String externalId;
|
||||
private final String userName;
|
||||
private final String emailAddress;
|
||||
private final String displayName;
|
||||
|
||||
public OAuthUserInfo(String externalId,
|
||||
String userName,
|
||||
String emailAddress,
|
||||
String displayName) {
|
||||
this.externalId = externalId;
|
||||
this.userName = userName;
|
||||
this.emailAddress = emailAddress;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getExternalId() {
|
||||
return externalId;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// 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.extensions.auth.oauth;
|
||||
|
||||
/* OAuth verifier */
|
||||
public class OAuthVerifier {
|
||||
|
||||
private final String value;
|
||||
|
||||
public OAuthVerifier(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -730,6 +730,15 @@ public class Gerrit implements EntryPoint {
|
||||
});
|
||||
break;
|
||||
|
||||
case OAUTH:
|
||||
menuRight.addItem(C.menuSignIn(), new Command() {
|
||||
@Override
|
||||
public void execute() {
|
||||
doSignIn(History.getToken());
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case OPENID_SSO:
|
||||
menuRight.addItem(C.menuSignIn(), new Command() {
|
||||
@Override
|
||||
|
@ -31,11 +31,13 @@ import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.TableRowElement;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.resources.client.CssResource;
|
||||
import com.google.gwt.uibinder.client.UiBinder;
|
||||
import com.google.gwt.uibinder.client.UiField;
|
||||
import com.google.gwt.uibinder.client.UiHandler;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.google.gwt.user.client.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
@ -64,7 +66,7 @@ class CommitBox extends Composite {
|
||||
@UiField Image mergeCommit;
|
||||
@UiField CopyableLabel commitName;
|
||||
@UiField FlowPanel webLinkPanel;
|
||||
@UiField Element parents;
|
||||
@UiField TableRowElement firstParent;
|
||||
@UiField FlowPanel parentCommits;
|
||||
@UiField FlowPanel parentWebLinks;
|
||||
@UiField InlineHyperlink authorNameEmail;
|
||||
@ -146,29 +148,56 @@ class CommitBox extends Composite {
|
||||
}
|
||||
|
||||
private void setParents(String project, JsArray<CommitInfo> commits) {
|
||||
setVisible(parents, true);
|
||||
setVisible(firstParent, true);
|
||||
TableRowElement next = firstParent;
|
||||
TableRowElement previous = null;
|
||||
for (CommitInfo c : Natives.asList(commits)) {
|
||||
CopyableLabel copyLabel = new CopyableLabel(c.commit());
|
||||
copyLabel.setTitle(c.subject());
|
||||
copyLabel.setStyleName(style.clippy());
|
||||
parentCommits.add(copyLabel);
|
||||
|
||||
GitwebLink gw = Gerrit.getGitwebLink();
|
||||
if (gw != null) {
|
||||
Anchor a =
|
||||
new Anchor(gw.getLinkName(), gw.toRevision(project, c.commit()));
|
||||
a.setStyleName(style.parentWebLink());
|
||||
parentWebLinks.add(a);
|
||||
if (next == firstParent) {
|
||||
CopyableLabel copyLabel = getCommitLabel(c);
|
||||
parentCommits.add(copyLabel);
|
||||
addLinks(project, c, parentWebLinks);
|
||||
} else {
|
||||
next.appendChild(DOM.createTD());
|
||||
Element td1 = DOM.createTD();
|
||||
td1.appendChild(getCommitLabel(c).getElement());
|
||||
next.appendChild(td1);
|
||||
FlowPanel linksPanel = new FlowPanel();
|
||||
linksPanel.addStyleName(style.parentWebLink());
|
||||
addLinks(project, c, linksPanel);
|
||||
Element td2 = DOM.createTD();
|
||||
td2.appendChild(linksPanel.getElement());
|
||||
next.appendChild(td2);
|
||||
previous.getParentElement().insertAfter(next, previous);
|
||||
}
|
||||
JsArray<WebLinkInfo> links = c.web_links();
|
||||
if (links != null) {
|
||||
for (WebLinkInfo link : Natives.asList(links)) {
|
||||
parentWebLinks.add(link.toAnchor());
|
||||
}
|
||||
previous = next;
|
||||
next = DOM.createTR().cast();
|
||||
}
|
||||
}
|
||||
|
||||
private void addLinks(String project, CommitInfo c, FlowPanel panel) {
|
||||
GitwebLink gw = Gerrit.getGitwebLink();
|
||||
if (gw != null) {
|
||||
Anchor a =
|
||||
new Anchor(gw.getLinkName(), gw.toRevision(project, c.commit()));
|
||||
a.setStyleName(style.parentWebLink());
|
||||
panel.add(a);
|
||||
}
|
||||
JsArray<WebLinkInfo> links = c.web_links();
|
||||
if (links != null) {
|
||||
for (WebLinkInfo link : Natives.asList(links)) {
|
||||
panel.add(link.toAnchor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CopyableLabel getCommitLabel(CommitInfo c) {
|
||||
CopyableLabel copyLabel;
|
||||
copyLabel = new CopyableLabel(c.commit());
|
||||
copyLabel.setTitle(c.subject());
|
||||
copyLabel.setStyleName(style.clippy());
|
||||
return copyLabel;
|
||||
}
|
||||
|
||||
private static void formatLink(GitPerson person, FlowPanel p,
|
||||
InlineHyperlink name, Element date, ChangeInfo change) {
|
||||
// only try to fetch the avatar image for author and committer if an avatar
|
||||
|
@ -162,7 +162,7 @@ limitations under the License.
|
||||
<g:FlowPanel ui:field='webLinkPanel' styleName='{style.webLinkPanel}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ui:field='parents' style='display: none'>
|
||||
<tr ui:field='firstParent' style='display: none'>
|
||||
<th><ui:msg>Parent(s)</ui:msg></th>
|
||||
<td>
|
||||
<g:FlowPanel ui:field='parentCommits'/>
|
||||
|
@ -124,11 +124,13 @@ limitations under the License.
|
||||
<div>↩</div>
|
||||
</g:Button>
|
||||
</g:HTMLPanel>
|
||||
<div ui:field='message'
|
||||
aria-hidden='true'
|
||||
style='display: NONE'
|
||||
styleName='{style.comment}'/>
|
||||
<g:FlowPanel ui:field='comments' visible='false'/>
|
||||
<div style='overflow: auto'>
|
||||
<div ui:field='message'
|
||||
aria-hidden='true'
|
||||
style='display: NONE'
|
||||
styleName='{style.comment}'/>
|
||||
<g:FlowPanel ui:field='comments' visible='false'/>
|
||||
</div>
|
||||
</div>
|
||||
</g:HTMLPanel>
|
||||
</ui:UiBinder>
|
||||
|
@ -26,6 +26,7 @@ import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.user.client.ui.CheckBox;
|
||||
import com.google.gwt.user.client.ui.SuggestBox;
|
||||
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
|
||||
import com.google.gwtexpui.globalkey.client.GlobalKey;
|
||||
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
|
||||
|
||||
import java.util.LinkedList;
|
||||
@ -81,10 +82,12 @@ public abstract class RebaseDialog extends CommentedActionDialog {
|
||||
public void onSuccess(ChangeList result) {
|
||||
changes = Natives.asList(result);
|
||||
base.setEnabled(true);
|
||||
base.setFocus(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
base.setEnabled(false);
|
||||
sendButton.setFocus(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -95,6 +98,13 @@ public abstract class RebaseDialog extends CommentedActionDialog {
|
||||
contentPanel.setStyleName(Gerrit.RESOURCES.css().rebaseContentPanel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void center() {
|
||||
super.center();
|
||||
GlobalKey.dialog(this);
|
||||
sendButton.setFocus(true);
|
||||
}
|
||||
|
||||
public String getBase() {
|
||||
return cb.getValue() ? base.getText() : null;
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ class GerritConfigProvider implements Provider<GerritConfig> {
|
||||
|
||||
case CLIENT_SSL_CERT_LDAP:
|
||||
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
||||
case OAUTH:
|
||||
case OPENID:
|
||||
case OPENID_SSO:
|
||||
break;
|
||||
|
@ -34,7 +34,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
class HttpLogoutServlet extends HttpServlet {
|
||||
public class HttpLogoutServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final DynamicItem<WebSession> webSession;
|
||||
@ -43,7 +43,7 @@ class HttpLogoutServlet extends HttpServlet {
|
||||
private final AuditService audit;
|
||||
|
||||
@Inject
|
||||
HttpLogoutServlet(final AuthConfig authConfig,
|
||||
protected HttpLogoutServlet(final AuthConfig authConfig,
|
||||
final DynamicItem<WebSession> webSession,
|
||||
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
||||
final AuditService audit) {
|
||||
@ -53,7 +53,7 @@ class HttpLogoutServlet extends HttpServlet {
|
||||
this.audit = audit;
|
||||
}
|
||||
|
||||
private void doLogout(final HttpServletRequest req,
|
||||
protected void doLogout(final HttpServletRequest req,
|
||||
final HttpServletResponse rsp) throws IOException {
|
||||
webSession.get().logout();
|
||||
if (logoutUrl != null) {
|
||||
|
@ -32,8 +32,10 @@ import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
|
||||
import com.google.gerrit.httpd.rpc.doc.QueryDocumentationFilter;
|
||||
import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
|
||||
import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
|
||||
import com.google.gerrit.reviewdb.client.AuthType;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gwtexpui.server.CacheControlFilter;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Provider;
|
||||
@ -50,9 +52,11 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
class UrlModule extends ServletModule {
|
||||
private GerritOptions options;
|
||||
private AuthConfig authConfig;
|
||||
|
||||
UrlModule(GerritOptions options) {
|
||||
UrlModule(GerritOptions options, AuthConfig authConfig) {
|
||||
this.options = options;
|
||||
this.authConfig = authConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,8 +70,11 @@ class UrlModule extends ServletModule {
|
||||
serve("/Gerrit/*").with(legacyGerritScreen());
|
||||
}
|
||||
serve("/cat/*").with(CatServlet.class);
|
||||
serve("/logout").with(HttpLogoutServlet.class);
|
||||
serve("/signout").with(HttpLogoutServlet.class);
|
||||
|
||||
if (authConfig.getAuthType() != AuthType.OAUTH) {
|
||||
serve("/logout").with(HttpLogoutServlet.class);
|
||||
serve("/signout").with(HttpLogoutServlet.class);
|
||||
}
|
||||
serve("/ssh_info").with(SshInfoServlet.class);
|
||||
serve("/static/*").with(StaticServlet.class);
|
||||
|
||||
|
@ -78,7 +78,7 @@ public class WebModule extends LifecycleModule {
|
||||
|
||||
installAuthModule();
|
||||
if (options.enableMasterFeatures()) {
|
||||
install(new UrlModule(options));
|
||||
install(new UrlModule(options, authConfig));
|
||||
install(new UiRpcModule());
|
||||
}
|
||||
install(new GerritRequestModule());
|
||||
@ -123,6 +123,8 @@ public class WebModule extends LifecycleModule {
|
||||
install(new BecomeAnyAccountModule());
|
||||
break;
|
||||
|
||||
case OAUTH:
|
||||
// OAuth support is bound in WebAppInitializer and Daemon.
|
||||
case OPENID:
|
||||
case OPENID_SSO:
|
||||
// OpenID support is bound in WebAppInitializer and Daemon.
|
||||
|
24
gerrit-oauth/BUCK
Normal file
24
gerrit-oauth/BUCK
Normal file
@ -0,0 +1,24 @@
|
||||
SRCS = glob(
|
||||
['src/main/java/**/*.java'],
|
||||
)
|
||||
RESOURCES = glob(['src/main/resources/**/*'])
|
||||
|
||||
java_library(
|
||||
name = 'oauth',
|
||||
srcs = SRCS,
|
||||
resources = RESOURCES,
|
||||
deps = [
|
||||
'//gerrit-common:annotations',
|
||||
'//gerrit-extension-api:api',
|
||||
'//gerrit-httpd:httpd',
|
||||
'//gerrit-server:server',
|
||||
'//lib:gson',
|
||||
'//lib:guava',
|
||||
'//lib/commons:codec',
|
||||
'//lib/guice:guice',
|
||||
'//lib/guice:guice-servlet',
|
||||
'//lib/log:api',
|
||||
],
|
||||
provided_deps = ['//lib:servlet-api-3_1'],
|
||||
visibility = ['PUBLIC'],
|
||||
)
|
@ -0,0 +1,56 @@
|
||||
// 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.oauth;
|
||||
|
||||
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 OAuthLogoutServlet extends HttpLogoutServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Provider<OAuthSession> oauthSession;
|
||||
|
||||
@Inject
|
||||
OAuthLogoutServlet(AuthConfig authConfig,
|
||||
DynamicItem<WebSession> webSession,
|
||||
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
|
||||
AuditService audit,
|
||||
Provider<OAuthSession> 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,31 @@
|
||||
// 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.oauth;
|
||||
|
||||
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
/** Servlets and support related to OAuth authentication. */
|
||||
public class OAuthModule extends ServletModule {
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
filter("/login", "/login/*", "/oauth").through(OAuthWebFilter.class);
|
||||
// This is needed to invalidate OAuth session during logout
|
||||
serve("/logout").with(OAuthLogoutServlet.class);
|
||||
DynamicMap.mapOf(binder(), OAuthServiceProvider.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
// 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.oauth;
|
||||
|
||||
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.httpd.WebSession;
|
||||
import com.google.gerrit.server.account.AccountException;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.account.AuthResult;
|
||||
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.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@SessionScoped
|
||||
/* OAuth protocol implementation */
|
||||
class OAuthSession {
|
||||
private static final Logger log = LoggerFactory.getLogger(OAuthSession.class);
|
||||
private static final SecureRandom randomState = newRandomGenerator();
|
||||
private final String state;
|
||||
private final DynamicItem<WebSession> webSession;
|
||||
private final AccountManager accountManager;
|
||||
private OAuthServiceProvider serviceProvider;
|
||||
private OAuthToken token;
|
||||
private OAuthUserInfo user;
|
||||
private String redirectUrl;
|
||||
|
||||
@Inject
|
||||
OAuthSession(DynamicItem<WebSession> webSession,
|
||||
AccountManager accountManager) {
|
||||
this.state = generateRandomState();
|
||||
this.webSession = webSession;
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
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(null,
|
||||
new OAuthVerifier(request.getParameter("code")));
|
||||
|
||||
user = oauth.getUserInfo(token);
|
||||
|
||||
if (isLoggedIn()) {
|
||||
log.debug("Login-SUCCESS " + this);
|
||||
authenticateAndRedirect(response);
|
||||
return true;
|
||||
} else {
|
||||
response.sendError(SC_UNAUTHORIZED);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log.debug("Login-PHASE1 " + this);
|
||||
redirectUrl = request.getRequestURI();
|
||||
response.sendRedirect(oauth.getAuthorizationUrl(null) +
|
||||
"&state=" + state);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void authenticateAndRedirect(HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
com.google.gerrit.server.account.AuthRequest areq =
|
||||
new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
|
||||
areq.setUserName(user.getUserName());
|
||||
areq.setEmailAddress(user.getEmailAddress());
|
||||
areq.setDisplayName(user.getDisplayName());
|
||||
AuthResult arsp;
|
||||
try {
|
||||
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);
|
||||
String suffix = redirectUrl.substring(
|
||||
OAuthWebFilter.GERRIT_LOGIN.length() + 1);
|
||||
suffix = URLDecoder.decode(suffix, StandardCharsets.UTF_8.name());
|
||||
rsp.sendRedirect(suffix);
|
||||
}
|
||||
|
||||
void logout() {
|
||||
token = null;
|
||||
user = null;
|
||||
redirectUrl = 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,223 @@
|
||||
// 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.oauth;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.httpd.HtmlDomUtil;
|
||||
import com.google.gerrit.httpd.LoginUrlToken;
|
||||
import com.google.gerrit.httpd.template.SiteHeaderFooter;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.ServletOutputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@Singleton
|
||||
/* OAuth web filter uses active OAuth session to perform OAuth requests */
|
||||
class OAuthWebFilter implements Filter {
|
||||
static final String GERRIT_LOGIN = "/login";
|
||||
|
||||
private final Provider<String> urlProvider;
|
||||
private final Provider<CurrentUser> currentUserProvider;
|
||||
private final Provider<OAuthSession> oauthSessionProvider;
|
||||
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
|
||||
private final SiteHeaderFooter header;
|
||||
private OAuthServiceProvider ssoProvider;
|
||||
|
||||
@Inject
|
||||
OAuthWebFilter(@CanonicalWebUrl @Nullable Provider<String> urlProvider,
|
||||
Provider<CurrentUser> currentUserProvider,
|
||||
DynamicMap<OAuthServiceProvider> oauthServiceProviders,
|
||||
Provider<OAuthSession> oauthSessionProvider,
|
||||
SiteHeaderFooter header) {
|
||||
this.urlProvider = urlProvider;
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
this.oauthServiceProviders = oauthServiceProviders;
|
||||
this.oauthSessionProvider = oauthSessionProvider;
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
String provider = httpRequest.getParameter("provider");
|
||||
OAuthSession oauthSession = oauthSessionProvider.get();
|
||||
OAuthServiceProvider service = ssoProvider == null
|
||||
? oauthSession.getServiceProvider()
|
||||
: ssoProvider;
|
||||
|
||||
if ((isGerritLogin(httpRequest)
|
||||
|| oauthSession.isOAuthFinal(httpRequest))
|
||||
&& !oauthSession.isLoggedIn()) {
|
||||
if (service == null && Strings.isNullOrEmpty(provider)) {
|
||||
selectProvider(httpRequest, httpResponse, null);
|
||||
return;
|
||||
} else {
|
||||
if (service == null) {
|
||||
service = findService(provider);
|
||||
}
|
||||
oauthSession.setServiceProvider(service);
|
||||
oauthSession.login(httpRequest, httpResponse, service);
|
||||
}
|
||||
} else {
|
||||
chain.doFilter(httpRequest, response);
|
||||
}
|
||||
}
|
||||
|
||||
private OAuthServiceProvider findService(String providerId)
|
||||
throws ServletException {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ServletException("No provider found for: " + providerId);
|
||||
}
|
||||
|
||||
private void selectProvider(HttpServletRequest req, HttpServletResponse res,
|
||||
@Nullable String errorMessage)
|
||||
throws IOException {
|
||||
String self = req.getRequestURI();
|
||||
String cancel = MoreObjects.firstNonNull(
|
||||
urlProvider != null ? urlProvider.get() : "/", "/");
|
||||
cancel += LoginUrlToken.getToken(req);
|
||||
|
||||
Document doc = header.parse(OAuthWebFilter.class, "LoginForm.html");
|
||||
HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName());
|
||||
HtmlDomUtil.find(doc, "login_form").setAttribute("action", self);
|
||||
HtmlDomUtil.find(doc, "cancel_link").setAttribute("href", cancel);
|
||||
|
||||
Element emsg = HtmlDomUtil.find(doc, "error_message");
|
||||
if (Strings.isNullOrEmpty(errorMessage)) {
|
||||
emsg.getParentNode().removeChild(emsg);
|
||||
} else {
|
||||
emsg.setTextContent(errorMessage);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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("?provider=%s_%s",
|
||||
pluginName, id));
|
||||
hyperlink.setTextContent(serviceName +
|
||||
" (" + pluginName + " plugin)");
|
||||
div.appendChild(hyperlink);
|
||||
form.appendChild(div);
|
||||
}
|
||||
|
||||
private static void sendHtml(HttpServletResponse res, Document doc)
|
||||
throws IOException {
|
||||
byte[] bin = HtmlDomUtil.toUTF8(doc);
|
||||
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
res.setContentType("text/html");
|
||||
res.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
res.setContentLength(bin.length);
|
||||
try (ServletOutputStream out = res.getOutputStream()) {
|
||||
out.write(bin);
|
||||
}
|
||||
}
|
||||
|
||||
private void pickSSOServiceProvider()
|
||||
throws ServletException {
|
||||
SortedSet<String> plugins = oauthServiceProviders.plugins();
|
||||
if (plugins.isEmpty()) {
|
||||
throw new ServletException(
|
||||
"OAuth service provider wasn't installed");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -100,6 +100,7 @@ java_library(
|
||||
':util',
|
||||
'//gerrit-cache-h2:cache-h2',
|
||||
'//gerrit-lucene:lucene',
|
||||
'//gerrit-oauth:oauth',
|
||||
'//gerrit-openid:openid',
|
||||
'//gerrit-solr:solr',
|
||||
'//lib:args4j',
|
||||
|
@ -29,6 +29,7 @@ import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
|
||||
import com.google.gerrit.httpd.RequestContextFilter;
|
||||
import com.google.gerrit.httpd.WebModule;
|
||||
import com.google.gerrit.httpd.WebSshGlueModule;
|
||||
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
|
||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
||||
import com.google.gerrit.httpd.plugins.HttpPluginModule;
|
||||
import com.google.gerrit.lifecycle.LifecycleManager;
|
||||
@ -434,6 +435,8 @@ public class Daemon extends SiteProgram {
|
||||
if (authConfig.getAuthType() == AuthType.OPENID ||
|
||||
authConfig.getAuthType() == AuthType.OPENID_SSO) {
|
||||
modules.add(new OpenIdModule());
|
||||
} else if (authConfig.getAuthType() == AuthType.OAUTH) {
|
||||
modules.add(new OAuthModule());
|
||||
}
|
||||
modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
|
||||
|
||||
|
@ -63,6 +63,7 @@ class InitAuth implements InitStep {
|
||||
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
||||
case LDAP:
|
||||
case LDAP_BIND:
|
||||
case OAUTH:
|
||||
case OPENID:
|
||||
case OPENID_SSO:
|
||||
break;
|
||||
@ -97,6 +98,7 @@ class InitAuth implements InitStep {
|
||||
case CUSTOM_EXTENSION:
|
||||
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
||||
case HTTP:
|
||||
case OAUTH:
|
||||
case OPENID:
|
||||
case OPENID_SSO:
|
||||
break;
|
||||
|
@ -80,5 +80,8 @@ public enum AuthType {
|
||||
CUSTOM_EXTENSION,
|
||||
|
||||
/** Development mode to enable becoming anyone you want. */
|
||||
DEVELOPMENT_BECOME_ANY_ACCOUNT
|
||||
DEVELOPMENT_BECOME_ANY_ACCOUNT,
|
||||
|
||||
/** Generic OAuth provider over HTTP. */
|
||||
OAUTH
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ public class DefaultRealm extends AbstractRealm {
|
||||
|
||||
@Override
|
||||
public boolean allowsEdit(final Account.FieldName field) {
|
||||
if (authConfig.getAuthType() == AuthType.HTTP) {
|
||||
if (authConfig.getAuthType() == AuthType.HTTP
|
||||
|| authConfig.getAuthType() == AuthType.OAUTH) {
|
||||
switch (field) {
|
||||
case USER_NAME:
|
||||
return false;
|
||||
|
@ -26,6 +26,7 @@ import com.google.gerrit.extensions.webui.UiAction;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Change.Status;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
|
||||
import com.google.gerrit.reviewdb.client.RevId;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.changedetail.RebaseChange;
|
||||
@ -42,6 +43,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Singleton
|
||||
public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
|
||||
UiAction<RevisionResource> {
|
||||
@ -110,6 +113,10 @@ public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
|
||||
} else if (baseChange.getStatus() == Status.ABANDONED) {
|
||||
throw new ResourceConflictException("base change is abandoned: "
|
||||
+ baseChange.getKey());
|
||||
} else if (isDescendantOf(baseChange.getId(), rsrc.getPatchSet().getRevision())) {
|
||||
throw new ResourceConflictException("base change " + baseChange.getKey()
|
||||
+ " is a descendant of the current "
|
||||
+ " change - recursion not allowed");
|
||||
}
|
||||
baseRev = basePatchSet.getRevision().get();
|
||||
break;
|
||||
@ -118,7 +125,7 @@ public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
|
||||
}
|
||||
|
||||
try {
|
||||
rebaseChange.get().rebase(rsrc.getChange(), rsrc.getPatchSet().getId(),
|
||||
rebaseChange.get().rebase(change, rsrc.getPatchSet().getId(),
|
||||
rsrc.getUser(), baseRev);
|
||||
} catch (InvalidChangeOperationException e) {
|
||||
throw new ResourceConflictException(e.getMessage());
|
||||
@ -131,6 +138,33 @@ public class Rebase implements RestModifyView<RevisionResource, RebaseInput>,
|
||||
return json.format(change.getId());
|
||||
}
|
||||
|
||||
private boolean isDescendantOf(Change.Id child, RevId ancestor)
|
||||
throws OrmException {
|
||||
ReviewDb db = dbProvider.get();
|
||||
|
||||
ArrayList<RevId> parents = new ArrayList<>();
|
||||
parents.add(ancestor);
|
||||
while (!parents.isEmpty()) {
|
||||
RevId parent = parents.remove(0);
|
||||
// get direct descendants of change
|
||||
for (PatchSetAncestor desc : db.patchSetAncestors().descendantsOf(parent)) {
|
||||
PatchSet descPatchSet = db.patchSets().get(desc.getPatchSet());
|
||||
Change.Id descChangeId = descPatchSet.getId().getParentKey();
|
||||
if (child.equals(descChangeId)) {
|
||||
PatchSet.Id descCurrentPatchSetId =
|
||||
db.changes().get(descChangeId).currentPatchSetId();
|
||||
// it's only bad if the descendant patch set is current
|
||||
return descPatchSet.getId().equals(descCurrentPatchSetId);
|
||||
} else {
|
||||
// process indirect descendants as well
|
||||
parents.add(descPatchSet.getRevision());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private PatchSet parseBase(final String base) throws OrmException {
|
||||
ReviewDb db = dbProvider.get();
|
||||
|
||||
|
@ -203,7 +203,7 @@ public class AuthConfig {
|
||||
case LDAP_BIND:
|
||||
case CLIENT_SSL_CERT_LDAP:
|
||||
case CUSTOM_EXTENSION:
|
||||
// Its safe to assume yes for an HTTP authentication type, as the
|
||||
case OAUTH:
|
||||
// only way in is through some external system that the admin trusts
|
||||
//
|
||||
return true;
|
||||
|
@ -1054,8 +1054,8 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
||||
} else {
|
||||
rc.unset(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
|
||||
}
|
||||
if (label.isCopyAllScoresIfNoChange()) {
|
||||
rc.setBoolean(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE, true);
|
||||
if (!label.isCopyAllScoresIfNoChange()) {
|
||||
rc.setBoolean(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE, false);
|
||||
} else {
|
||||
rc.unset(LABEL, name, KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ java_library(
|
||||
'//gerrit-extension-api:api',
|
||||
'//gerrit-httpd:httpd',
|
||||
'//gerrit-lucene:lucene',
|
||||
'//gerrit-oauth:oauth',
|
||||
'//gerrit-openid:openid',
|
||||
'//gerrit-pgm:http',
|
||||
'//gerrit-pgm:init',
|
||||
|
@ -19,6 +19,7 @@ import static com.google.inject.Stage.PRODUCTION;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.gerrit.common.ChangeHookRunner;
|
||||
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
|
||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
||||
import com.google.gerrit.httpd.plugins.HttpPluginModule;
|
||||
import com.google.gerrit.lifecycle.LifecycleManager;
|
||||
@ -346,6 +347,8 @@ public class WebAppInitializer extends GuiceServletContextListener
|
||||
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
|
||||
if (authConfig.getAuthType() == AuthType.OPENID) {
|
||||
modules.add(new OpenIdModule());
|
||||
} else if (authConfig.getAuthType() == AuthType.OAUTH) {
|
||||
modules.add(new OAuthModule());
|
||||
}
|
||||
modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user