Settings screen for OAuth tokens

The new screen is visible if authentication type OAUTH is enabled
for Git over HTTP communication (auth.gitBasicAuth = true).
It shows the value of the access token that is assigned to the
current web session, the expiration date of the token, and the
corresponding entries for the .netrc and .gitcookies files.

Change-Id: I4fad66f53ba8cc872de8f8a2120239c83624e9d9
Signed-off-by: Michael Ochmann <michael.ochmann@sap.com>
This commit is contained in:
Michael Ochmann
2016-01-07 17:02:27 +01:00
committed by Saša Živkov
parent e56acd6cca
commit f698212ed0
10 changed files with 329 additions and 1 deletions

View File

@@ -29,6 +29,7 @@ public class PageLinks {
public static final String SETTINGS_SSHKEYS = "/settings/ssh-keys"; public static final String SETTINGS_SSHKEYS = "/settings/ssh-keys";
public static final String SETTINGS_GPGKEYS = "/settings/gpg-keys"; public static final String SETTINGS_GPGKEYS = "/settings/gpg-keys";
public static final String SETTINGS_HTTP_PASSWORD = "/settings/http-password"; public static final String SETTINGS_HTTP_PASSWORD = "/settings/http-password";
public static final String SETTINGS_OAUTH_TOKEN = "/settings/oauth-token";
public static final String SETTINGS_WEBIDENT = "/settings/web-identities"; public static final String SETTINGS_WEBIDENT = "/settings/web-identities";
public static final String SETTINGS_MYGROUPS = "/settings/group-memberships"; public static final String SETTINGS_MYGROUPS = "/settings/group-memberships";
public static final String SETTINGS_AGREEMENTS = "/settings/agreements"; public static final String SETTINGS_AGREEMENTS = "/settings/agreements";

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2016 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.client.info;
import com.google.gwt.core.client.JavaScriptObject;
public class OAuthTokenInfo extends JavaScriptObject {
protected OAuthTokenInfo() {
}
public final native String username() /*-{ return this.username; }-*/;
public final native String resourceHost() /*-{ return this.resource_host; }-*/;
public final native String accessToken() /*-{ return this.access_token; }-*/;
public final native String providerId() /*-{ return this.provider_id; }-*/;
public final native String expiresAt() /*-{ return this.expires_at; }-*/;
public final native String type() /*-{ return this.type; }-*/;
}

View File

@@ -33,6 +33,7 @@ import static com.google.gerrit.common.PageLinks.SETTINGS_EDIT_PREFERENCES;
import static com.google.gerrit.common.PageLinks.SETTINGS_EXTENSION; import static com.google.gerrit.common.PageLinks.SETTINGS_EXTENSION;
import static com.google.gerrit.common.PageLinks.SETTINGS_GPGKEYS; import static com.google.gerrit.common.PageLinks.SETTINGS_GPGKEYS;
import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD; import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
import static com.google.gerrit.common.PageLinks.SETTINGS_OAUTH_TOKEN;
import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS; import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS;
import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT; import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT;
import static com.google.gerrit.common.PageLinks.SETTINGS_PREFERENCES; import static com.google.gerrit.common.PageLinks.SETTINGS_PREFERENCES;
@@ -48,6 +49,7 @@ import com.google.gerrit.client.account.MyEditPreferencesScreen;
import com.google.gerrit.client.account.MyGpgKeysScreen; import com.google.gerrit.client.account.MyGpgKeysScreen;
import com.google.gerrit.client.account.MyGroupsScreen; import com.google.gerrit.client.account.MyGroupsScreen;
import com.google.gerrit.client.account.MyIdentitiesScreen; import com.google.gerrit.client.account.MyIdentitiesScreen;
import com.google.gerrit.client.account.MyOAuthTokenScreen;
import com.google.gerrit.client.account.MyPasswordScreen; import com.google.gerrit.client.account.MyPasswordScreen;
import com.google.gerrit.client.account.MyPreferencesScreen; import com.google.gerrit.client.account.MyPreferencesScreen;
import com.google.gerrit.client.account.MyProfileScreen; import com.google.gerrit.client.account.MyProfileScreen;
@@ -570,6 +572,12 @@ public class Dispatcher {
return new MyPasswordScreen(); return new MyPasswordScreen();
} }
if (matchExact(SETTINGS_OAUTH_TOKEN, token)
&& Gerrit.info().auth().isOAuth()
&& Gerrit.info().auth().isGitBasicAuth()) {
return new MyOAuthTokenScreen();
}
if (matchExact(MY_GROUPS, token) if (matchExact(MY_GROUPS, token)
|| matchExact(SETTINGS_MYGROUPS, token)) { || matchExact(SETTINGS_MYGROUPS, token)) {
return new MyGroupsScreen(); return new MyGroupsScreen();

View File

@@ -105,6 +105,14 @@ public interface GerritCss extends CssResource {
String menuScreenMenuBar(); String menuScreenMenuBar();
String needsReview(); String needsReview();
String negscore(); String negscore();
String oauthExpires();
String oauthInfoBlock();
String oauthPanel();
String oauthPanelCookieEntry();
String oauthPanelCookieHeading();
String oauthPanelNetRCEntry();
String oauthPanelNetRCHeading();
String oauthToken();
String pagingLink(); String pagingLink();
String patchSetActions(); String patchSetActions();
String pluginProjectConfigInheritedValue(); String pluginProjectConfigInheritedValue();

View File

@@ -57,6 +57,7 @@ public interface AccountConstants extends Constants {
String tabGpgKeys(); String tabGpgKeys();
String tabHttpAccess(); String tabHttpAccess();
String tabMyGroups(); String tabMyGroups();
String tabOAuthToken();
String tabPreferences(); String tabPreferences();
String tabSshKeys(); String tabSshKeys();
String tabWatchedProjects(); String tabWatchedProjects();
@@ -81,6 +82,12 @@ public interface AccountConstants extends Constants {
String invalidUserName(); String invalidUserName();
String invalidUserEmail(); String invalidUserEmail();
String labelOAuthToken();
String labelOAuthExpires();
String labelOAuthNetRCEntry();
String labelOAuthGitCookie();
String labelOAuthExpired();
String sshKeyInvalid(); String sshKeyInvalid();
String sshKeyAlgorithm(); String sshKeyAlgorithm();
String sshKeyKey(); String sshKeyKey();

View File

@@ -44,6 +44,7 @@ tabDiffPreferences = Diff Preferences
tabEditPreferences = Edit Preferences tabEditPreferences = Edit Preferences
tabGpgKeys = GPG Public Keys tabGpgKeys = GPG Public Keys
tabHttpAccess = HTTP Password tabHttpAccess = HTTP Password
tabOAuthToken = OAuth Token
tabMyGroups = Groups tabMyGroups = Groups
tabPreferences = Preferences tabPreferences = Preferences
tabSshKeys = SSH Public Keys tabSshKeys = SSH Public Keys
@@ -68,6 +69,13 @@ linkEditFullName = Edit
linkReloadContact = Reload linkReloadContact = Reload
invalidUserName = Username must contain only letters, numbers, _, - or . invalidUserName = Username must contain only letters, numbers, _, - or .
invalidUserEmail = Email format is wrong. invalidUserEmail = Email format is wrong.
labelOAuthToken = Access Token
labelOAuthExpires = Expires
labelOAuthNetRCEntry = Entry for ~/.netrc
labelOAuthGitCookie = Entry for ~/.gitcookies
labelOAuthExpired = To obtain an access token please sign out and sign in again.
sshKeyInvalid = Invalid Key sshKeyInvalid = Invalid Key
sshKeyAlgorithm = Algorithm sshKeyAlgorithm = Algorithm
sshKeyKey = Key sshKeyKey = Key

View File

@@ -0,0 +1,197 @@
// Copyright (C) 2016 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.client.account;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.info.GeneralPreferences;
import com.google.gerrit.client.info.OAuthTokenInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import java.util.Date;
public class MyOAuthTokenScreen extends SettingsScreen {
private CopyableLabel tokenLabel;
private Label expiresLabel;
private Label expiredNote;
private CopyableLabel netrcValue;
private CopyableLabel cookieValue;
private FlowPanel flow;
private Grid grid;
@Override
protected void onInitUI() {
super.onInitUI();
tokenLabel = new CopyableLabel("");
tokenLabel.addStyleName(Gerrit.RESOURCES.css().oauthToken());
expiresLabel = new Label("");
expiresLabel.addStyleName(Gerrit.RESOURCES.css().oauthExpires());
grid = new Grid(2, 2);
grid.setStyleName(Gerrit.RESOURCES.css().infoBlock());
grid.addStyleName(Gerrit.RESOURCES.css().oauthInfoBlock());
add(grid);
expiredNote = new Label(Util.C.labelOAuthExpired());
expiredNote.setVisible(false);
add(expiredNote);
row(grid, 0, Util.C.labelOAuthToken(), tokenLabel);
row(grid, 1, Util.C.labelOAuthExpires(), expiresLabel);
CellFormatter fmt = grid.getCellFormatter();
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
flow = new FlowPanel();
flow.setStyleName(Gerrit.RESOURCES.css().oauthPanel());
add(flow);
Label netrcLabel = new Label(Util.C.labelOAuthNetRCEntry());
netrcLabel.setStyleName(Gerrit.RESOURCES.css().oauthPanelNetRCHeading());
flow.add(netrcLabel);
netrcValue= new CopyableLabel("");
netrcValue.setStyleName(Gerrit.RESOURCES.css().oauthPanelNetRCEntry());
flow.add(netrcValue);
Label cookieLabel = new Label(Util.C.labelOAuthGitCookie());
cookieLabel.setStyleName(Gerrit.RESOURCES.css().oauthPanelCookieHeading());
flow.add(cookieLabel);
cookieValue = new CopyableLabel("");
cookieValue.setStyleName(Gerrit.RESOURCES.css().oauthPanelCookieEntry());
flow.add(cookieValue);
}
private void row(Grid grid, int row, String name, Widget field) {
final CellFormatter fmt = grid.getCellFormatter();
if (LocaleInfo.getCurrentLocale().isRTL()) {
grid.setText(row, 1, name);
grid.setWidget(row, 0, field);
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().header());
} else {
grid.setText(row, 0, name);
grid.setWidget(row, 1, field);
fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().header());
}
}
@Override
protected void onLoad() {
super.onLoad();
AccountApi.self().view("preferences")
.get(new ScreenLoadCallback<GeneralPreferences>(this) {
@Override
protected void preDisplay(GeneralPreferences prefs) {
display(prefs);
}
});
}
private void display(final GeneralPreferences prefs) {
AccountApi.self().view("oauthtoken")
.get(new GerritCallback<OAuthTokenInfo>() {
@Override
public void onSuccess(OAuthTokenInfo tokenInfo) {
tokenLabel.setText(tokenInfo.accessToken());
expiresLabel.setText(getExpiresAt(tokenInfo, prefs));
netrcValue.setText(getNetRC(tokenInfo));
cookieValue.setText(getCookie(tokenInfo));
flow.setVisible(true);
expiredNote.setVisible(false);
}
@Override
public void onFailure(Throwable caught) {
if (isNoSuchEntity(caught) || isSigninFailure(caught)) {
tokenLabel.setText("");
expiresLabel.setText("");
netrcValue.setText("");
cookieValue.setText("");
flow.setVisible(false);
expiredNote.setVisible(true);
} else {
showFailure(caught);
}
}
});
}
private static long getExpiresAt(OAuthTokenInfo tokenInfo) {
if (tokenInfo.expiresAt() == null) {
return Long.MAX_VALUE;
}
long expiresAt;
try {
expiresAt = Long.parseLong(tokenInfo.expiresAt());
} catch (NumberFormatException e) {
return Long.MAX_VALUE;
}
return expiresAt;
}
private static long getExpiresAtSeconds(OAuthTokenInfo tokenInfo) {
return getExpiresAt(tokenInfo) / 1000L;
}
private static String getExpiresAt(OAuthTokenInfo tokenInfo,
GeneralPreferences prefs) {
long expiresAt = getExpiresAt(tokenInfo);
if (expiresAt == Long.MAX_VALUE) {
return "";
}
String dateFormat = prefs.dateFormat().getLongFormat();
String timeFormat = prefs.timeFormat().getFormat();
DateTimeFormat formatter = DateTimeFormat.getFormat(
dateFormat + " " + timeFormat);
return formatter.format(new Date(expiresAt));
}
private static String getNetRC(OAuthTokenInfo accessTokenInfo) {
StringBuilder sb = new StringBuilder();
sb.append("machine ");
sb.append(accessTokenInfo.resourceHost());
sb.append(" login ");
sb.append(accessTokenInfo.username());
sb.append(" password ");
sb.append(accessTokenInfo.accessToken());
return sb.toString();
}
private static String getCookie(OAuthTokenInfo accessTokenInfo) {
StringBuilder sb = new StringBuilder();
sb.append(accessTokenInfo.resourceHost());
sb.append("\tFALSE\t/\tTRUE\t");
sb.append(getExpiresAtSeconds(accessTokenInfo));
sb.append("\tgit-");
sb.append(accessTokenInfo.username());
sb.append('\t');
sb.append(accessTokenInfo.accessToken());
if (accessTokenInfo.providerId() != null) {
sb.append('@').append(accessTokenInfo.providerId());
}
return sb.toString();
}
}

View File

@@ -47,6 +47,10 @@ public abstract class SettingsScreen extends MenuScreen {
if (Gerrit.info().auth().isHttpPasswordSettingsEnabled()) { if (Gerrit.info().auth().isHttpPasswordSettingsEnabled()) {
linkByGerrit(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD); linkByGerrit(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
} }
if (Gerrit.info().auth().isOAuth()
&& Gerrit.info().auth().isGitBasicAuth()) {
linkByGerrit(Util.C.tabOAuthToken(), PageLinks.SETTINGS_OAUTH_TOKEN);
}
if (Gerrit.info().gerrit().editGpgKeys()) { if (Gerrit.info().gerrit().editGpgKeys()) {
linkByGerrit(Util.C.tabGpgKeys(), PageLinks.SETTINGS_GPGKEYS); linkByGerrit(Util.C.tabGpgKeys(), PageLinks.SETTINGS_GPGKEYS);
} }

View File

@@ -828,6 +828,70 @@ a:hover.downloadLink {
margin-bottom: 10px; margin-bottom: 10px;
} }
.oauthInfoBlock {
margin-bottom: 10px;
}
.oauthToken {
font-family: monospace;
font-size: small;
width: 40em;
}
.oauthToken span {
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
width: 38em;
}
.oauthExpires {
font-family: monospace;
font-size: small;
width: 40em;
}
.oauthPanel {
margin-top: 10px;
border: 1px solid trimColor;
padding: 5px 5px 5px 5px;
}
.oauthPanelNetRCHeading {
margin-top: 5px;
margin-left: 1em;
white-space: nowrap;
}
.oauthPanelNetRCEntry {
margin-top: 5px;
margin-left: 2em;
font-family: monospace;
font-size: small;
width: 80em;
}
.oauthPanelNetRCEntry span {
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
width: 78em;
}
.oauthPanelCookieHeading {
margin-top: 15px;
margin-left: 1em;
white-space: nowrap;
}
.oauthPanelCookieEntry {
margin-top: 5px;
margin-left: 2em;
font-family: monospace;
font-size: small;
width: 80em;
}
.oauthPanelCookieEntry span {
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
width: 78em;
}
/** CommentedActionDialog **/ /** CommentedActionDialog **/
.commentedActionDialog .gwt-DisclosurePanel .header td { .commentedActionDialog .gwt-DisclosurePanel .header td {

View File

@@ -132,6 +132,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
info.useContributorAgreements = toBoolean(cfg.isUseContributorAgreements()); info.useContributorAgreements = toBoolean(cfg.isUseContributorAgreements());
info.editableAccountFields = new ArrayList<>(realm.getEditableFields()); info.editableAccountFields = new ArrayList<>(realm.getEditableFields());
info.switchAccountUrl = cfg.getSwitchAccountUrl(); info.switchAccountUrl = cfg.getSwitchAccountUrl();
info.isGitBasicAuth = toBoolean(cfg.isGitBasicAuth());
switch (info.authType) { switch (info.authType) {
case LDAP: case LDAP:
@@ -139,7 +140,6 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
info.registerUrl = cfg.getRegisterUrl(); info.registerUrl = cfg.getRegisterUrl();
info.registerText = cfg.getRegisterText(); info.registerText = cfg.getRegisterText();
info.editFullNameUrl = cfg.getEditFullNameUrl(); info.editFullNameUrl = cfg.getEditFullNameUrl();
info.isGitBasicAuth = toBoolean(cfg.isGitBasicAuth());
break; break;
case CUSTOM_EXTENSION: case CUSTOM_EXTENSION: