/* * RHQ Management Platform * Copyright (C) 2005-2010 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.coregui.client.admin.users; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.gwt.user.client.rpc.AsyncCallback; import com.smartgwt.client.data.DSRequest; import com.smartgwt.client.data.DSResponse; import com.smartgwt.client.data.DataSourceField; import com.smartgwt.client.data.Record; import com.smartgwt.client.data.fields.DataSourceIntegerField; import com.smartgwt.client.data.fields.DataSourcePasswordField; import com.smartgwt.client.data.fields.DataSourceTextField; import com.smartgwt.client.types.FieldType; import com.smartgwt.client.widgets.form.validator.LengthRangeValidator; import com.smartgwt.client.widgets.form.validator.MatchesFieldValidator; import com.smartgwt.client.widgets.form.validator.RegExpValidator; import com.smartgwt.client.widgets.grid.ListGridRecord; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.criteria.SubjectCriteria; import org.rhq.core.domain.util.PageList; import org.rhq.coregui.client.admin.roles.RolesDataSource; import org.rhq.coregui.client.gwt.GWTServiceLookup; import org.rhq.coregui.client.gwt.SubjectGWTServiceAsync; import org.rhq.coregui.client.util.RPCDataSource; import org.rhq.coregui.client.util.validator.EmailValidator; /** * A DataSource for RHQ {@link Subject user}s. * * @author Greg Hinkle * @author Ian Springer */ public class UsersDataSource extends RPCDataSource<Subject, SubjectCriteria> { private static UsersDataSource INSTANCE; private static final String MASKED_PASSWORD_VALUE = "XXXXXXXX"; public static abstract class Field { public static final String ID = "id"; public static final String NAME = "name"; public static final String FIRST_NAME = "firstName"; public static final String LAST_NAME = "lastName"; public static final String FACTIVE = "factive"; public static final String FSYSTEM = "fsystem"; public static final String DEPARTMENT = "department"; public static final String PHONE_NUMBER = "phoneNumber"; public static final String EMAIL_ADDRESS = "emailAddress"; public static final String ROLES = "roles"; // auth-related fields public static final String LDAP = "ldap"; public static final String PASSWORD = "password"; public static final String PASSWORD_VERIFY = "passwordVerify"; } public static final int ID_OVERLORD = 1; public static final int ID_RHQADMIN = 2; private final SubjectGWTServiceAsync subjectService = GWTServiceLookup.getSubjectService(); public static UsersDataSource getInstance() { if (INSTANCE == null) { INSTANCE = new UsersDataSource(); } return INSTANCE; } public static boolean isSystemSubjectId(int subjectId) { return (subjectId == ID_OVERLORD || subjectId == ID_RHQADMIN); } public UsersDataSource() { List<DataSourceField> fields = addDataSourceFields(); addFields(fields); } @Override protected List<DataSourceField> addDataSourceFields() { List<DataSourceField> fields = super.addDataSourceFields(); DataSourceField idDataField = new DataSourceIntegerField(Field.ID, "ID"); idDataField.setPrimaryKey(true); idDataField.setCanEdit(false); fields.add(idDataField); DataSourceTextField usernameField = createTextField(Field.NAME, MSG.common_title_username(), 3, 100, true); // Don't allow characters that could be used in HTML intended for an XSS attack. RegExpValidator regExpValidator = new RegExpValidator("[^&<]*"); usernameField.setValidators(regExpValidator); fields.add(usernameField); DataSourceTextField ldapField = createBooleanField(Field.LDAP, MSG.dataSource_users_field_ldap(), true); ldapField.setCanEdit(false); // read-only fields.add(ldapField); DataSourcePasswordField passwordField = new DataSourcePasswordField(Field.PASSWORD, MSG.common_title_password(), 100, true); LengthRangeValidator passwordValidator = new LengthRangeValidator(); passwordValidator.setMin(6); passwordValidator.setMax(100); passwordField.setValidators(passwordValidator); fields.add(passwordField); DataSourcePasswordField passwordVerifyField = new DataSourcePasswordField(Field.PASSWORD_VERIFY, MSG.dataSource_users_field_passwordVerify(), 100, true); MatchesFieldValidator passwordsEqualValidator = new MatchesFieldValidator(); passwordsEqualValidator.setOtherField(Field.PASSWORD); passwordsEqualValidator.setErrorMessage(MSG.dataSource_users_passwordsDoNotMatch()); passwordVerifyField.setValidators(passwordsEqualValidator); fields.add(passwordVerifyField); DataSourceTextField firstNameField = createTextField(Field.FIRST_NAME, MSG.dataSource_users_field_firstName(), null, 100, true); fields.add(firstNameField); DataSourceTextField lastNameField = createTextField(Field.LAST_NAME, MSG.dataSource_users_field_lastName(), null, 100, true); fields.add(lastNameField); DataSourceTextField emailAddressField = createTextField(Field.EMAIL_ADDRESS, MSG.dataSource_users_field_emailAddress(), null, 100, true); fields.add(emailAddressField); EmailValidator emailAddressValidator = new EmailValidator(); emailAddressValidator.setErrorMessage(MSG.dataSource_users_invalidEmailAddress()); emailAddressField.setValidators(emailAddressValidator); DataSourceTextField phoneNumberField = createTextField(Field.PHONE_NUMBER, MSG.dataSource_users_field_phoneNumber(), null, 100, false); fields.add(phoneNumberField); DataSourceTextField departmentField = createTextField(Field.DEPARTMENT, MSG.dataSource_users_field_department(), null, 100, false); fields.add(departmentField); DataSourceTextField enabledField = createBooleanField(Field.FACTIVE, MSG.dataSource_users_field_factive(), true); fields.add(enabledField); DataSourceField rolesField = new DataSourceField(Field.ROLES, FieldType.ANY, "Roles"); fields.add(rolesField); return fields; } @Override public void executeFetch(final DSRequest request, final DSResponse response, final SubjectCriteria criteria) { subjectService.findSubjectsByCriteria(criteria, new AsyncCallback<PageList<Subject>>() { public void onFailure(Throwable caught) { String message = "Failed to fetch user(s)."; sendFailureResponse(request, response, message, caught); } public void onSuccess(final PageList<Subject> fetchedSubjects) { final PageList<Record> userRecordsPageList = new PageList<Record>(fetchedSubjects.getPageControl()); userRecordsPageList.setTotalSize(fetchedSubjects.getTotalSize()); userRecordsPageList.setUnbounded(fetchedSubjects.isUnbounded()); final boolean[] failed = { false }; for (int i = 0, fetchedSubjectsSize = fetchedSubjects.size(); i < fetchedSubjectsSize; i++) { if (failed[0]) { break; } final Subject fetchedSubject = fetchedSubjects.get(i); final String username = fetchedSubject.getName(); subjectService.isUserWithPrincipal(username, new AsyncCallback<Boolean>() { public void onFailure(Throwable caught) { failed[0] = true; String message = "Failed to check if user [" + username + "] is an LDAP user."; sendFailureResponse(request, response, message, caught); } public void onSuccess(Boolean hasPrincipal) { boolean isLdap = (!hasPrincipal); Record userRecord = copyUserValues(fetchedSubject, isLdap); userRecordsPageList.add(userRecord); if (userRecordsPageList.size() == fetchedSubjects.size()) { sendSuccessResponseRecords(request, response, userRecordsPageList); } } }); } } }); } @Override protected void executeAdd(final Record recordToAdd, final DSRequest request, final DSResponse response) { final Subject newSubject = copyValues(recordToAdd); String password = recordToAdd.getAttribute(Field.PASSWORD); subjectService.createSubject(newSubject, password, new AsyncCallback<Subject>() { public void onFailure(Throwable caught) { // TODO: Throw more specific SLSB exceptions so we can set the right validation errors. String message = caught.getMessage(); if (message != null && message.contains("javax.persistence.EntityExistsException")) { Map<String, String> errorMessages = new HashMap<String, String>(); errorMessages.put(Field.NAME, MSG.view_adminUsers_failCreateUserWithExistingName(newSubject.getName())); sendValidationErrorResponse(request, response, errorMessages); } else { throw new RuntimeException(caught); } } public void onSuccess(final Subject createdSubject) { Record createdUserRecord = copyUserValues(createdSubject, false); sendSuccessResponse(request, response, createdUserRecord); } }); } @Override protected void executeUpdate(final Record editedUserRecord, Record oldUserRecord, final DSRequest request, final DSResponse response) { Subject editedSubject = copyValues(editedUserRecord); final String username = editedSubject.getName(); final String editedPassword = editedUserRecord.getAttributeAsString(Field.PASSWORD); boolean passwordWasEdited = !MASKED_PASSWORD_VALUE.equals(editedPassword); String newPassword = (passwordWasEdited) ? editedPassword : null; subjectService.updateSubject(editedSubject, newPassword, new AsyncCallback<Subject>() { public void onFailure(Throwable caught) { String message = "Failed to update user [" + username + "]."; sendFailureResponse(request, response, message, caught); } public void onSuccess(final Subject updatedSubject) { sendSuccessResponse(request, response, editedUserRecord); } }); } public Subject copyValues(Record from) { Subject to = new Subject(); to.setId(from.getAttributeAsInt(Field.ID)); to.setName(from.getAttributeAsString(Field.NAME)); to.setFirstName(from.getAttributeAsString(Field.FIRST_NAME)); to.setLastName(from.getAttributeAsString(Field.LAST_NAME)); to.setFactive(Boolean.valueOf(from.getAttributeAsString(Field.FACTIVE))); to.setFsystem(Boolean.valueOf(from.getAttributeAsString(Field.FSYSTEM))); to.setDepartment(from.getAttributeAsString(Field.DEPARTMENT)); to.setPhoneNumber(from.getAttributeAsString(Field.PHONE_NUMBER)); to.setEmailAddress(from.getAttributeAsString(Field.EMAIL_ADDRESS)); Record[] roleRecords = from.getAttributeAsRecordArray(Field.ROLES); to.setRoles(RolesDataSource.getInstance().buildDataObjects(roleRecords)); return to; } public Record copyUserValues(Subject subject, boolean isLdap) { ListGridRecord targetRecord = copyValues(subject); targetRecord.setAttribute(Field.LDAP, isLdap); // Leave the password fields blank if username is null (i.e. it's a new user). if (subject.getName() != null) { targetRecord.setAttribute(Field.PASSWORD, MASKED_PASSWORD_VALUE); targetRecord.setAttribute(Field.PASSWORD_VERIFY, MASKED_PASSWORD_VALUE); } return targetRecord; } public ListGridRecord copyValues(Subject from) { return copyValues(from, true); } @Override public ListGridRecord copyValues(Subject from, boolean cascade) { ListGridRecord to = new ListGridRecord(); to.setAttribute(Field.ID, from.getId()); to.setAttribute(Field.NAME, from.getName()); to.setAttribute(Field.FIRST_NAME, from.getFirstName()); to.setAttribute(Field.LAST_NAME, from.getLastName()); to.setAttribute(Field.FACTIVE, from.getFactive()); to.setAttribute(Field.FSYSTEM, from.getFsystem()); to.setAttribute(Field.DEPARTMENT, from.getDepartment()); to.setAttribute(Field.PHONE_NUMBER, from.getPhoneNumber()); to.setAttribute(Field.EMAIL_ADDRESS, from.getEmailAddress()); if (cascade) { ListGridRecord[] roleRecords = RolesDataSource.getInstance().buildRecords(from.getRoles(), false); to.setAttribute(Field.ROLES, roleRecords); } return to; } @Override protected SubjectCriteria getFetchCriteria(DSRequest request) { SubjectCriteria criteria = new SubjectCriteria(); // Filtering Integer subjectId = getFilter(request, Field.ID, Integer.class); criteria.addFilterId(subjectId); // Always filter out the overlord - mortal users need not know the overlord even exists. criteria.addFilterFsystem(false); // Fetching if (subjectId != null) { // If we're fetching a single Subject, then fetch the related Set of Roles. criteria.fetchRoles(true); } // TODO: For the list view, use a composite object that will pull the role // count across the wire. this count will not require permission checks at all. return criteria; } @Override protected String getSortFieldForColumn(String columnName) { // this is a calculated field, can't perform server-side sort if (Field.LDAP.equals(columnName)) { return null; } return super.getSortFieldForColumn(columnName); } }