/*
* Copyright 2011-2017 Amazon Technologies, Inc.
*
* 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://aws.amazon.com/apache2.0
*
* This file 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.amazonaws.eclipse.codecommit.widgets;
import static com.amazonaws.eclipse.core.model.GitCredentialsDataModel.P_PASSWORD;
import static com.amazonaws.eclipse.core.model.GitCredentialsDataModel.P_SHOW_PASSWORD;
import static com.amazonaws.eclipse.core.model.GitCredentialsDataModel.P_USERNAME;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newLink;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newPushButton;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import com.amazonaws.eclipse.codecommit.CodeCommitConstants;
import com.amazonaws.eclipse.codecommit.credentials.GitCredential;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.model.GitCredentialsDataModel;
import com.amazonaws.eclipse.core.ui.WebLinkListener;
import com.amazonaws.eclipse.core.widget.CheckboxComplex;
import com.amazonaws.eclipse.core.widget.TextComplex;
import com.amazonaws.eclipse.databinding.NotEmptyValidator;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagement;
import com.amazonaws.services.identitymanagement.model.AttachUserPolicyRequest;
import com.amazonaws.services.identitymanagement.model.CreateServiceSpecificCredentialRequest;
import com.amazonaws.services.identitymanagement.model.CreateUserRequest;
import com.amazonaws.services.identitymanagement.model.LimitExceededException;
import com.amazonaws.services.identitymanagement.model.ListUsersRequest;
import com.amazonaws.services.identitymanagement.model.ListUsersResult;
import com.amazonaws.services.identitymanagement.model.ServiceSpecificCredential;
import com.amazonaws.services.identitymanagement.model.User;
import com.amazonaws.util.StringUtils;
/**
* A complex composite for configuring Git credentials.
*/
public class GitCredentialsComposite extends Composite {
private static final String AUTO_CREATE_GIT_CREDENTIALS_TILTE = "Auto-create Git credentials";
private static final String IAM_USER_PREFIX = "EclipseToolkit-CodeCommitUser";
private static final String GIT_CREDENTIALS_DOC =
"http://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html#setting-up-gc-iam";
private static final String CREATE_SERVICE_SPECIFIC_CREDENTIALS_DOC =
"http://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateServiceSpecificCredential.html";
private final DataBindingContext dataBindingContext;
private final GitCredentialsDataModel dataModel;
private TextComplex usernameComplex;
private TextComplex passwordComplex;
private CheckboxComplex showPasswordComplex;
private Button browseButton;
private Button createGitCredentialsButton;
private IValidator usernameValidator;
private IValidator passwordValidator;
public GitCredentialsComposite(Composite parent, DataBindingContext dataBindingContext, GitCredentialsDataModel dataModel) {
this(parent, dataBindingContext, dataModel, null, null);
}
public GitCredentialsComposite(Composite parent, DataBindingContext dataBindingContext, GitCredentialsDataModel dataModel,
IValidator usernameValidator, IValidator passwordValidator) {
super(parent, SWT.NONE);
setLayout(new GridLayout(2, false));
setLayoutData(new GridData(GridData.FILL_BOTH));
this.dataModel = dataModel;
this.dataBindingContext = dataBindingContext;
this.usernameValidator = usernameValidator;
this.passwordValidator = passwordValidator;
createControl();
}
public void populateGitCredential(String username, String password) {
usernameComplex.setText(username);
passwordComplex.setText(password);
}
private void createControl() {
createUsernamePasswordSection();
createButtonCompositeSection();
onShowPasswordCheckboxSelection();
}
private void createUsernamePasswordSection() {
newLink(this, new WebLinkListener(), String.format(
"You can manually copy and paste Git credentials for AWS CodeCommit below. "
+ "Alternately, you can import them from a downloaded .csv file. To learn how to generate Git credentials, see "
+ "Create Git Credentials for HTTPS Connections to AWS CodeCommit. You can also authorize the AWS "
+ "Toolkit for Eclipse to create a new set of Git credentials under the current selected account. see "
+ "CreateServiceSpecificCredential for more information.", GIT_CREDENTIALS_DOC, CREATE_SERVICE_SPECIFIC_CREDENTIALS_DOC), 2);
usernameComplex = TextComplex.builder(this, dataBindingContext, PojoObservables.observeValue(dataModel, P_USERNAME))
.addValidator(usernameValidator == null ? new NotEmptyValidator("User name must be provided!") : usernameValidator)
.labelValue("User name:")
.defaultValue(dataModel.getUsername())
.build();
passwordComplex = TextComplex.builder(this, dataBindingContext, PojoObservables.observeValue(dataModel, P_PASSWORD))
.addValidator(passwordValidator == null ? new NotEmptyValidator("Password must be provided!") : passwordValidator)
.labelValue("Password: ")
.defaultValue(dataModel.getPassword())
.build();
}
private void createButtonCompositeSection() {
Composite buttonComposite = new Composite(this, SWT.NONE);
buttonComposite.setLayout(new GridLayout(3, false));
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = 2;
buttonComposite.setLayoutData(gridData);
showPasswordComplex = CheckboxComplex.builder()
.composite(buttonComposite)
.dataBindingContext(dataBindingContext)
.pojoObservableValue(PojoObservables.observeValue(dataModel, P_SHOW_PASSWORD))
.labelValue("Show password")
.selectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onShowPasswordCheckboxSelection();
}
})
.defaultValue(dataModel.isShowPassword())
.build();
browseButton = newPushButton(buttonComposite, "Import from csv file");
browseButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog dialog = new FileDialog(getShell(), SWT.SINGLE);
String path = dialog.open();
if (path == null) {
return;
}
GitCredential gitCredential = loadGitCredential(new File(path));
populateGitCredential(gitCredential.getUsername(), gitCredential.getPassword());
}
});
createGitCredentialsButton = newPushButton(buttonComposite, "Create Git credentials");
createGitCredentialsButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
createGitCredentialsButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
AmazonIdentityManagement iam = dataModel.getIamClient();
String profileName = AwsToolkitCore.getDefault().getAccountManager().getAccountInfo(dataModel.getUserAccount()).getAccountName();
try {
String userName = iam.getUser().getUser().getUserName();
if (StringUtils.isNullOrEmpty(userName)) {
if (!MessageDialog.openConfirm(getShell(), AUTO_CREATE_GIT_CREDENTIALS_TILTE,
String.format("Your profile is using root AWS credentials. AWS CodeCommit requires specific CodeCommit credentials from an IAM user. "
+ "The toolkit can create an IAM user with CodeCommit credentials and associate the credentials with the %s Toolkit profile.\n\n"
+ "Proceed to try and create an IAM user with credentials and associate with the %s Toolkit profile?", profileName, profileName))) {
return;
}
userName = createNewIamUser().getUserName();
}
ServiceSpecificCredential credential = iam.createServiceSpecificCredential(new CreateServiceSpecificCredentialRequest()
.withUserName(userName).withServiceName(CodeCommitConstants.CODECOMMIT_SERVICE_NAME))
.getServiceSpecificCredential();
populateGitCredential(credential.getServiceUserName(), credential.getServicePassword());
} catch (LimitExceededException lee) {
MessageDialog.openWarning(getShell(), AUTO_CREATE_GIT_CREDENTIALS_TILTE, "You may already have created the maximum number of sets of Git credentials for AWS CodeCommit(two). "
+ "Log into the AWS Management Console to download the credentials or obtain them from your administrator, and then import to the toolkit.");
} catch (Exception ex) {
MessageDialog.openError(getShell(), AUTO_CREATE_GIT_CREDENTIALS_TILTE, "Error: " + ex.getMessage());
}
}
});
}
// Create a new IAM user attched with the default policy, AWSCodeCommitPowerUser
private User createNewIamUser() {
AmazonIdentityManagement iam = dataModel.getIamClient();
Set userSet = new HashSet<>();
ListUsersResult result = new ListUsersResult();
do {
result = iam.listUsers(new ListUsersRequest().withMarker(result.getMarker()));
for (User user : result.getUsers()) {
userSet.add(user.getUserName());
}
} while (result.isTruncated());
String newIamUserName = IAM_USER_PREFIX;
for (int i = 1; userSet.contains(newIamUserName); ++i) {
newIamUserName = IAM_USER_PREFIX + "-" + i;
}
User newUser = iam.createUser(new CreateUserRequest().withUserName(newIamUserName)).getUser();
iam.attachUserPolicy(new AttachUserPolicyRequest()
.withUserName(newIamUserName)
.withPolicyArn("arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"));
return newUser;
}
private void onShowPasswordCheckboxSelection() {
passwordComplex.getText().setEchoChar(showPasswordComplex.getCheckbox().getSelection() ? '\0' : '*');
}
private GitCredential loadGitCredential(File csvFile) {
GitCredential gitCredential = new GitCredential("", "");
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(csvFile))) {
String line = bufferedReader.readLine(); // the first line of the default csv file is metadata
if (line == null) {
throw new ParseException("The csv file is empty", 1);
}
line = bufferedReader.readLine(); // the second line of the default csv file contains the credentials separated with ','
if (line == null) {
throw new ParseException("Invalid Git credential csv file format!", 2);
}
String[] tokens = line.split(",");
if (tokens.length != 2) {
throw new ParseException(
"The csv file must have two columns!", 2);
}
gitCredential.setUsername(tokens[0].trim());
gitCredential.setPassword(tokens[1].trim());
} catch (Exception e) {
AwsToolkitCore.getDefault().logWarning("Failed to load gitCredentials file for Git credentials!", e);
new MessageDialog(getShell(), "Error loading Git credentials!",
null, e.getMessage(), MessageDialog.ERROR, new String[] {"OK"}, 0).open();
}
return gitCredential;
}
}