describe('RGW users page', () => {
const users = new UsersPageHelper();
- const user_name = 'e2e_000user_create_edit_delete';
+ const tenant = 'e2e_000tenant';
+ const user_id = 'e2e_000user_create_edit_delete';
+ const user_name = tenant + '$' + user_id;
beforeEach(() => {
cy.login();
describe('create, edit & delete user tests', () => {
it('should create user', () => {
users.navigateTo('create');
- users.create(user_name, 'Some Name', 'original@website.com', '1200');
- users.getFirstTableCell(user_name).should('exist');
+ users.create(tenant, user_id, 'Some Name', 'original@website.com', '1200');
+ users.getFirstTableCell(user_id).should('exist');
});
it('should edit users full name, email and max buckets', () => {
pages = pages;
@PageHelper.restrictTo(pages.create.url)
- create(username: string, fullname: string, email: string, maxbuckets: string) {
- // Enter in username
- cy.get('#uid').type(username);
-
+ create(tenant: string, user_id: string, fullname: string, email: string, maxbuckets: string) {
+ // Enter in user_id
+ cy.get('#user_id').type(user_id);
+ // Show Tenanat
+ cy.get('#show_tenant').click({ force: true });
+ // Enter in tenant
+ cy.get('#tenant').type(tenant);
// Enter in full name
cy.get('#display_name').click().type(fullname);
// Click the create button and wait for user to be made
cy.contains('button', 'Create User').click();
- this.getFirstTableCell(username).should('exist');
+ this.getFirstTableCell(tenant + '$' + user_id).should('exist');
}
@PageHelper.restrictTo(pages.index.url)
}
invalidCreate() {
+ const tenant = '000invalid_tenant';
const uname = '000invalid_create_user';
// creating this user in order to check that you can't give two users the same name
this.navigateTo('create');
- this.create(uname, 'xxx', 'xxx@xxx', '1');
+ this.create(tenant, uname, 'xxx', 'xxx@xxx', '1');
this.navigateTo('create');
// Username
- cy.get('#uid')
+ cy.get('#user_id')
// No username had been entered. Field should be invalid
.should('have.class', 'ng-invalid')
// Try to give user already taken name. Should make field invalid.
- .type(uname)
- .blur()
- .should('have.class', 'ng-invalid');
- cy.contains('#uid + .invalid-feedback', 'The chosen user ID is already in use.');
+ .type(uname);
+ cy.get('#show_tenant').click({ force: true });
+ cy.get('#tenant').type(tenant).should('have.class', 'ng-invalid');
+ cy.contains('#tenant + .invalid-feedback', 'The chosen user ID exists in this tenant.');
// check that username field is marked invalid if username has been cleared off
- cy.get('#uid').clear().blur().should('have.class', 'ng-invalid');
- cy.contains('#uid + .invalid-feedback', 'This field is required.');
+ cy.get('#user_id').clear().blur().should('have.class', 'ng-invalid');
+ cy.contains('#user_id + .invalid-feedback', 'This field is required.');
// Full name
cy.get('#display_name')
cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
this.navigateTo();
- this.delete(uname);
+ this.delete(tenant + '$' + uname);
}
invalidEdit() {
+ const tenant = '000invalid_tenant';
const uname = '000invalid_edit_user';
// creating this user to edit for the test
this.navigateTo('create');
- this.create(uname, 'xxx', 'xxx@xxx', '50');
+ this.create(tenant, uname, 'xxx', 'xxx@xxx', '50');
this.navigateEdit(name);
cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.');
this.navigateTo();
- this.delete(uname);
+ this.delete(tenant + '$' + uname);
}
}
<div *ngIf="user">
<table class="table table-striped table-bordered">
<tbody>
+ <tr>
+ <td i18n
+ class="bold w-25">Tenant</td>
+ <td class="w-75">{{ user.tenant }}</td>
+ </tr>
+ <tr>
+ <td i18n
+ class="bold w-25">User ID</td>
+ <td class="w-75">{{ user.user_id }}</td>
+ </tr>
<tr>
<td i18n
class="bold w-25">Username</td>
const detailsTab = fixture.debugElement.nativeElement.querySelectorAll(
'.table.table-striped.table-bordered tr td'
);
- expect(detailsTab[6].textContent).toEqual('System');
- expect(detailsTab[7].textContent).toEqual('Yes');
+ expect(detailsTab[10].textContent).toEqual('System');
+ expect(detailsTab[11].textContent).toEqual('Yes');
component.selection.system = 'false';
component.ngOnChanges();
fixture.detectChanges();
- expect(detailsTab[7].textContent).toEqual('No');
+ expect(detailsTab[11].textContent).toEqual('No');
});
});
class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
<div class="card-body">
- <!-- Username -->
+ <!-- User ID -->
<div class="form-group row">
<label class="cd-col-form-label"
[ngClass]="{'required': !editing}"
- for="uid"
- i18n>Username</label>
+ for="user_id"
+ i18n>User ID</label>
<div class="cd-col-form-input">
- <input id="uid"
+ <input id="user_id"
class="form-control"
type="text"
- formControlName="uid"
+ formControlName="user_id"
+ [readonly]="editing">
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('user_id', frm, 'required')"
+ i18n>This field is required.</span>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('user_id', frm, 'pattern')"
+ i18n>The value is not valid.</span>
+ <span class="invalid-feedback"
+ *ngIf="!userForm.getValue('show_tenant') && userForm.showError('user_id', frm, 'notUnique')"
+ i18n>The chosen user ID is already in use.</span>
+ </div>
+ </div>
+
+ <!-- Show Tenant -->
+ <div class="form-group row">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-checkbox">
+ <input class="custom-control-input"
+ id="show_tenant"
+ type="checkbox"
+ (click)="updateFieldsWhenTenanted()"
+ formControlName="show_tenant"
+ [readonly]="true">
+ <label class="custom-control-label"
+ for="show_tenant"
+ i18n>Show Tenant</label>
+ </div>
+ </div>
+ </div>
+
+ <!-- Tenant -->
+ <div class="form-group row"
+ *ngIf="userForm.getValue('show_tenant')">
+ <label class="cd-col-form-label"
+ for="tenant"
+ i18n>Tenant</label>
+ <div class="cd-col-form-input">
+ <input id="tenant"
+ class="form-control"
+ type="text"
+ formControlName="tenant"
[readonly]="editing"
autofocus>
<span class="invalid-feedback"
- *ngIf="userForm.showError('uid', frm, 'required')"
- i18n>This field is required.</span>
+ *ngIf="userForm.showError('tenant', frm, 'pattern')"
+ i18n>The value is not valid.</span>
<span class="invalid-feedback"
- *ngIf="userForm.showError('uid', frm, 'notUnique')"
- i18n>The chosen user ID is already in use.</span>
+ *ngIf="userForm.showError('tenant', frm, 'notUnique')"
+ i18n>The chosen user ID exists in this tenant.</span>
</div>
</div>
describe('username validation', () => {
it('should validate that username is required', () => {
- formHelper.expectErrorChange('uid', '', 'required', true);
+ formHelper.expectErrorChange('user_id', '', 'required', true);
});
it('should validate that username is valid', fakeAsync(() => {
spyOn(rgwUserService, 'get').and.returnValue(throwError('foo'));
- formHelper.setValue('uid', 'ab', true);
+ formHelper.setValue('user_id', 'ab', true);
tick(500);
- formHelper.expectValid('uid');
+ formHelper.expectValid('user_id');
}));
it('should validate that username is invalid', fakeAsync(() => {
spyOn(rgwUserService, 'get').and.returnValue(observableOf({}));
- formHelper.setValue('uid', 'abc', true);
+ formHelper.setValue('user_id', 'abc', true);
tick(500);
- formHelper.expectError('uid', 'notUnique');
+ formHelper.expectError('user_id', 'notUnique');
}));
});
subuserLabel: string;
s3keyLabel: string;
capabilityLabel: string;
+ usernameExists: boolean;
+ showTenant = false;
+ previousTenant: string = null;
constructor(
private formBuilder: CdFormBuilder,
createForm() {
this.userForm = this.formBuilder.group({
// General
- uid: [
+ user_id: [
null,
- [Validators.required],
- this.editing ? [] : [CdValidators.unique(this.rgwUserService.exists, this.rgwUserService)]
+ [Validators.required, Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)],
+ this.editing
+ ? []
+ : [
+ CdValidators.unique(this.rgwUserService.exists, this.rgwUserService, () =>
+ this.userForm.getValue('tenant')
+ )
+ ]
+ ],
+ show_tenant: [this.editing],
+ tenant: [
+ null,
+ [Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)],
+ this.editing
+ ? []
+ : [
+ CdValidators.unique(
+ this.rgwUserService.exists,
+ this.rgwUserService,
+ () => this.userForm.getValue('user_id'),
+ true
+ )
+ ]
],
display_name: [null, [Validators.required]],
email: [
this.goToListView();
return;
}
- const uid = this.userForm.getValue('uid');
+ const uid = this.getUID();
if (this.editing) {
// Edit
if (this._isGeneralDirty()) {
});
}
+ updateFieldsWhenTenanted() {
+ this.showTenant = this.userForm.getValue('show_tenant');
+ if (!this.showTenant) {
+ this.userForm.get('user_id').markAsUntouched();
+ this.userForm.get('tenant').patchValue(this.previousTenant);
+ } else {
+ this.userForm.get('user_id').markAsTouched();
+ this.previousTenant = this.userForm.get('tenant').value;
+ this.userForm.get('tenant').patchValue(null);
+ }
+ }
+
+ getUID(): string {
+ let uid = this.userForm.getValue('user_id');
+ let tenant: any;
+ if (this.userForm !== null) {
+ tenant = this.userForm.getValue('tenant');
+ }
+ if (tenant && tenant.length > 0) {
+ uid = `${this.userForm.getValue('tenant')}$${uid}`;
+ }
+ return uid;
+ }
+
/**
* Validate the quota maximum size, e.g. 1096, 1K, 30M or 1.9MiB.
*/
'full-control': 'full',
'read-write': 'readwrite'
};
- const uid = this.userForm.getValue('uid');
+ const uid = this.getUID();
const args = {
subuser: subuser.id,
access:
deleteSubuser(index: number) {
const subuser = this.subusers[index];
// Create an observable to delete the subuser when the form is submitted.
- this.submitObservables.push(
- this.rgwUserService.deleteSubuser(this.userForm.getValue('uid'), subuser.id)
- );
+ this.submitObservables.push(this.rgwUserService.deleteSubuser(this.getUID(), subuser.id));
// Remove the associated S3 keys.
this.s3Keys = this.s3Keys.filter((key) => {
return key.user !== subuser.id;
* Add/Update a capability.
*/
setCapability(cap: RgwUserCapability, index?: number) {
- const uid = this.userForm.getValue('uid');
+ const uid = this.getUID();
if (_.isNumber(index)) {
// Modify
const oldCap = this.capabilities[index];
const cap = this.capabilities[index];
// Create an observable to delete the capability when the form is submitted.
this.submitObservables.push(
- this.rgwUserService.deleteCapability(this.userForm.getValue('uid'), cap.type, cap.perm)
+ this.rgwUserService.deleteCapability(this.getUID(), cap.type, cap.perm)
);
// Remove the capability to update the UI.
this.capabilities.splice(index, 1);
deleteS3Key(index: number) {
const key = this.s3Keys[index];
// Create an observable to delete the S3 key when the form is submitted.
- this.submitObservables.push(
- this.rgwUserService.deleteS3Key(this.userForm.getValue('uid'), key.access_key)
- );
+ this.submitObservables.push(this.rgwUserService.deleteS3Key(this.getUID(), key.access_key));
// Remove the S3 key to update the UI.
this.s3Keys.splice(index, 1);
// Mark the form as dirty to be able to submit it.
* @param {number | undefined} index The subuser to show.
*/
showSubuserModal(index?: number) {
- const uid = this.userForm.getValue('uid');
+ const uid = this.getUID();
const modalRef = this.bsModalService.show(RgwUserSubuserModalComponent);
if (_.isNumber(index)) {
// Edit
*/
private _getCreateArgs() {
const result = {
- uid: this.userForm.getValue('uid'),
+ uid: this.getUID(),
display_name: this.userForm.getValue('display_name'),
suspended: this.userForm.getValue('suspended'),
email: '',
private _getS3KeyUserCandidates() {
let result = [];
// Add the current user id.
- const uid = this.userForm.getValue('uid');
+ const uid = this.getUID();
if (_.isString(uid) && !_.isEmpty(uid)) {
result.push(uid);
}
prop: 'uid',
flexGrow: 1
},
+ {
+ name: this.i18n('Tenant'),
+ prop: 'tenant',
+ flexGrow: 1
+ },
{
name: this.i18n('Full name'),
prop: 'display_name',
static unique(
serviceFn: existsServiceFn,
serviceFnThis: any = null,
+ usernameFn?: Function,
+ uidfield = false,
dueTime = 500
): AsyncValidatorFn {
+ let uname: string;
return (control: AbstractControl): Observable<ValidationErrors | null> => {
// Exit immediately if user has not interacted with the control yet
// or the control value is empty.
if (control.pristine || isEmptyInputValue(control.value)) {
return observableOf(null);
}
+ uname = control.value;
+ if (_.isFunction(usernameFn) && usernameFn() !== null && usernameFn() !== '') {
+ if (uidfield) {
+ uname = `${control.value}$${usernameFn()}`;
+ } else {
+ uname = `${usernameFn()}$${control.value}`;
+ }
+ }
+
// Forgot previous requests if a new one arrives within the specified
// delay time.
return observableTimer(dueTime).pipe(
- switchMapTo(serviceFn.call(serviceFnThis, control.value)),
+ switchMapTo(serviceFn.call(serviceFnThis, uname)),
map((resp: boolean) => {
if (!resp) {
return null;