feat: allow usernames without @ when UserMustBeDomain false (#4852)

* feat: allow usernames without @ when UserMustBeDomain false

* e2e

* test(e2e): table driven tests for humans and machines

* cleanup

* fix(e2e): ensure there are no username conflicts

* e2e: make awaitDesired async

* rm settings mapping

* e2e: make awaitDesired async

* e2e: parse sequence as int

* e2e: ensure test fails if awaitDesired fails

Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
Livio Spring 2022-12-22 12:16:17 +01:00 committed by GitHub
parent 7d9fc2c6e7
commit 0530f19d94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 498 additions and 106 deletions

View File

@ -1,19 +1,24 @@
import { apiAuth } from '../../support/api/apiauth'; import { apiAuth } from '../../support/api/apiauth';
import { ensureHumanUserExists, ensureUserDoesntExist } from '../../support/api/users'; import { ensureHumanUserExists, ensureUserDoesntExist } from '../../support/api/users';
import { loginname } from '../../support/login/users'; import { loginname } from '../../support/login/users';
import { ensureDomainPolicy } from '../../support/api/policies';
describe('humans', () => { describe('humans', () => {
const humansPath = `/users?type=human`; const humansPath = `/users?type=human`;
const testHumanUserNameAdd = 'e2ehumanusernameadd';
const testHumanUserNameRemove = 'e2ehumanusernameremove';
[
{ mustBeDomain: false, addName: 'e2ehumanusernameaddGlobal', removeName: 'e2ehumanusernameremoveGlobal' },
{ mustBeDomain: false, addName: 'e2ehumanusernameadd@test.com', removeName: 'e2ehumanusernameremove@test.com' },
{ mustBeDomain: true, addName: 'e2ehumanusernameadd', removeName: 'e2ehumanusernameremove' },
].forEach((user) => {
beforeEach(() => { beforeEach(() => {
apiAuth().as('api'); apiAuth().as('api');
}); });
describe('add', () => { describe(`add "${user.addName}" with domain setting "${user.mustBeDomain}"`, () => {
beforeEach(`ensure it doesn't exist already`, function () { beforeEach(`ensure it doesn't exist already`, function () {
ensureUserDoesntExist(this.api, loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION'))); ensureDomainPolicy(this.api, user.mustBeDomain, true, false);
ensureUserDoesntExist(this.api, user.addName);
cy.visit(humansPath); cy.visit(humansPath);
}); });
@ -22,35 +27,41 @@ describe('humans', () => {
cy.url().should('contain', 'users/create'); cy.url().should('contain', 'users/create');
cy.get('[formcontrolname="email"]').type('dummy@dummy.com'); cy.get('[formcontrolname="email"]').type('dummy@dummy.com');
//force needed due to the prefilled username prefix //force needed due to the prefilled username prefix
cy.get('[formcontrolname="userName"]').type(loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION'))); cy.get('[formcontrolname="userName"]').type(user.addName);
cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname'); cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname');
cy.get('[formcontrolname="lastName"]').type('e2ehumanlastname'); cy.get('[formcontrolname="lastName"]').type('e2ehumanlastname');
cy.get('[formcontrolname="phone"]').type('+41 123456789'); cy.get('[formcontrolname="phone"]').type('+41 123456789');
cy.get('[data-e2e="create-button"]').click(); cy.get('[data-e2e="create-button"]').click();
cy.get('.data-e2e-success'); cy.get('.data-e2e-success');
const loginName = loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION')); let loginName = user.addName;
if (user.mustBeDomain) {
loginName = loginname(user.addName, Cypress.env('ORGANIZATION'));
}
cy.contains('[data-e2e="copy-loginname"]', loginName).click(); cy.contains('[data-e2e="copy-loginname"]', loginName).click();
cy.clipboardMatches(loginName); cy.clipboardMatches(loginName);
cy.shouldNotExist({ selector: '.data-e2e-failure' }); cy.shouldNotExist({ selector: '.data-e2e-failure' });
}); });
}); });
describe('remove', () => { describe(`remove "${user.removeName}" with domain setting "${user.mustBeDomain}"`, () => {
beforeEach('ensure it exists', function () { beforeEach('ensure it exists', function () {
ensureHumanUserExists(this.api, loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION'))); ensureHumanUserExists(this.api, user.removeName);
cy.visit(humansPath); cy.visit(humansPath);
}); });
let loginName = user.removeName;
if (user.mustBeDomain) {
loginName = loginname(user.removeName, Cypress.env('ORGANIZATION'));
}
it('should delete a human user', () => { it('should delete a human user', () => {
const rowSelector = `tr:contains(${testHumanUserNameRemove})`; const rowSelector = `tr:contains(${user.removeName})`;
cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true }); cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true });
cy.get('[data-e2e="confirm-dialog-input"]') cy.get('[data-e2e="confirm-dialog-input"]').focus().type(loginName);
.focus()
.type(loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION')));
cy.get('[data-e2e="confirm-dialog-button"]').click(); cy.get('[data-e2e="confirm-dialog-button"]').click();
cy.get('.data-e2e-success'); cy.get('.data-e2e-success');
cy.shouldNotExist({ selector: rowSelector, timeout: 2000 }); cy.shouldNotExist({ selector: rowSelector, timeout: 2000 });
cy.shouldNotExist({ selector: '.data-e2e-failure' }); cy.shouldNotExist({ selector: '.data-e2e-failure' });
}); });
}); });
});
}); });

View File

@ -1,19 +1,24 @@
import { apiAuth } from '../../support/api/apiauth'; import { apiAuth } from '../../support/api/apiauth';
import { ensureMachineUserExists, ensureUserDoesntExist } from '../../support/api/users'; import { ensureMachineUserExists, ensureUserDoesntExist } from '../../support/api/users';
import { loginname } from '../../support/login/users'; import { loginname } from '../../support/login/users';
import { ensureDomainPolicy } from '../../support/api/policies';
describe('machines', () => { describe('machines', () => {
const machinesPath = `/users?type=machine`;
beforeEach(() => { beforeEach(() => {
apiAuth().as('api'); apiAuth().as('api');
}); });
const machinesPath = `/users?type=machine`; [
const testMachineUserNameAdd = 'e2emachineusernameadd'; { mustBeDomain: false, addName: 'e2emachineusernameaddGlobal', removeName: 'e2emachineusernameremoveGlobal' },
const testMachineUserNameRemove = 'e2emachineusernameremove'; { mustBeDomain: false, addName: 'e2emachineusernameadd@test.com', removeName: 'e2emachineusernameremove@test.com' },
{ mustBeDomain: true, addName: 'e2emachineusernameadd', removeName: 'e2emachineusernameremove' },
describe('add', () => { ].forEach((machine) => {
describe(`add "${machine.addName}" with domain setting "${machine.mustBeDomain}"`, () => {
beforeEach(`ensure it doesn't exist already`, function () { beforeEach(`ensure it doesn't exist already`, function () {
ensureUserDoesntExist(this.api, testMachineUserNameAdd); ensureDomainPolicy(this.api, machine.mustBeDomain, false, false);
ensureUserDoesntExist(this.api, machine.addName);
cy.visit(machinesPath); cy.visit(machinesPath);
}); });
@ -21,27 +26,35 @@ describe('machines', () => {
cy.get('[data-e2e="create-user-button"]').click(); cy.get('[data-e2e="create-user-button"]').click();
cy.url().should('contain', 'users/create-machine'); cy.url().should('contain', 'users/create-machine');
//force needed due to the prefilled username prefix //force needed due to the prefilled username prefix
cy.get('[formcontrolname="userName"]').type(testMachineUserNameAdd); cy.get('[formcontrolname="userName"]').type(machine.addName);
cy.get('[formcontrolname="name"]').type('e2emachinename'); cy.get('[formcontrolname="name"]').type('e2emachinename');
cy.get('[formcontrolname="description"]').type('e2emachinedescription'); cy.get('[formcontrolname="description"]').type('e2emachinedescription');
cy.get('[data-e2e="create-button"]').click(); cy.get('[data-e2e="create-button"]').click();
cy.get('.data-e2e-success'); cy.get('.data-e2e-success');
cy.contains('[data-e2e="copy-loginname"]', testMachineUserNameAdd).click(); let loginName = machine.addName;
cy.clipboardMatches(testMachineUserNameAdd); if (machine.mustBeDomain) {
loginName = loginname(machine.addName, Cypress.env('ORGANIZATION'));
}
cy.contains('[data-e2e="copy-loginname"]', loginName).click();
cy.clipboardMatches(loginName);
cy.shouldNotExist({ selector: '.data-e2e-failure' }); cy.shouldNotExist({ selector: '.data-e2e-failure' });
}); });
}); });
describe('edit', () => { describe(`remove "${machine.removeName}" with domain setting "${machine.mustBeDomain}"`, () => {
beforeEach('ensure it exists', function () { beforeEach('ensure it exists', function () {
ensureMachineUserExists(this.api, testMachineUserNameRemove); ensureMachineUserExists(this.api, machine.removeName);
cy.visit(machinesPath); cy.visit(machinesPath);
}); });
let loginName = machine.removeName;
if (machine.mustBeDomain) {
loginName = loginname(machine.removeName, Cypress.env('ORGANIZATION'));
}
it('should delete a machine', () => { it('should delete a machine', () => {
const rowSelector = `tr:contains(${testMachineUserNameRemove})`; const rowSelector = `tr:contains(${machine.removeName})`;
cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true }); cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true });
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testMachineUserNameRemove); cy.get('[data-e2e="confirm-dialog-input"]').focus().type(loginName);
cy.get('[data-e2e="confirm-dialog-button"]').click(); cy.get('[data-e2e="confirm-dialog-button"]').click();
cy.get('.data-e2e-success'); cy.get('.data-e2e-success');
cy.shouldNotExist({ selector: rowSelector, timeout: 2000 }); cy.shouldNotExist({ selector: rowSelector, timeout: 2000 });
@ -50,4 +63,5 @@ describe('machines', () => {
it('should create a personal access token'); it('should create a personal access token');
}); });
});
}); });

View File

@ -56,7 +56,6 @@ export function ensureSetting(
'PUT', 'PUT',
body, body,
(entity) => !!entity, (entity) => !!entity,
(body) => body?.settings?.id,
); );
} }
@ -66,14 +65,15 @@ function awaitDesired(
search: () => Cypress.Chainable<SearchResult>, search: () => Cypress.Chainable<SearchResult>,
initialSequence?: number, initialSequence?: number,
) { ) {
search().then((resp) => { return search().then((resp) => {
const foundExpectedEntity = expectEntity(resp.entity); const foundExpectedEntity = expectEntity(resp.entity);
const foundExpectedSequence = !initialSequence || resp.sequence >= initialSequence; const foundExpectedSequence = !initialSequence || resp.sequence >= initialSequence;
if (!foundExpectedEntity || !foundExpectedSequence) { const check = !foundExpectedEntity || !foundExpectedSequence;
if (check) {
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0); expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
cy.wait(1000); cy.wait(1000);
awaitDesired(trials - 1, expectEntity, search, initialSequence); return awaitDesired(trials - 1, expectEntity, search, initialSequence);
} }
}); });
} }
@ -117,7 +117,8 @@ export function ensureSomething(
}); });
}) })
.then((data) => { .then((data) => {
awaitDesired(90, expectEntity, search, data.sequence); return awaitDesired(90, expectEntity, search, data.sequence).then(() => {
return cy.wrap<number>(data.id); return cy.wrap<number>(data.id);
}); });
});
} }

View File

@ -12,7 +12,7 @@ export function ensureOrgExists(api: API, name: string): Cypress.Chainable<numbe
encodeURI(`${api.mgmtBaseURL}/global/orgs/_by_domain?domain=${name}.${host(Cypress.config('baseUrl'))}`), encodeURI(`${api.mgmtBaseURL}/global/orgs/_by_domain?domain=${name}.${host(Cypress.config('baseUrl'))}`),
'GET', 'GET',
(res) => { (res) => {
return { entity: res.org, id: res.org?.id, sequence: res.org?.details?.sequence }; return { entity: res.org, id: res.org?.id, sequence: parseInt(<string>res.org?.details?.sequence) };
}, },
), ),
() => `${api.mgmtBaseURL}/orgs`, () => `${api.mgmtBaseURL}/orgs`,
@ -25,6 +25,6 @@ export function ensureOrgExists(api: API, name: string): Cypress.Chainable<numbe
export function getOrgUnderTest(api: API): Cypress.Chainable<number> { export function getOrgUnderTest(api: API): Cypress.Chainable<number> {
return searchSomething(api, `${api.mgmtBaseURL}/orgs/me`, 'GET', (res) => { return searchSomething(api, `${api.mgmtBaseURL}/orgs/me`, 'GET', (res) => {
return { entity: res.org, id: res.org.id, sequence: res.org.details.sequence }; return { entity: res.org, id: res.org.id, sequence: parseInt(<string>res.org.details.sequence) };
}).then((res) => res.entity.id); }).then((res) => res.entity.id);
} }

View File

@ -1,5 +1,6 @@
import { requestHeaders } from './apiauth'; import { requestHeaders } from './apiauth';
import { API } from './types'; import { API } from './types';
import { ensureSetting } from './ensure';
export enum Policy { export enum Policy {
Label = 'label', Label = 'label',
@ -15,3 +16,38 @@ export function resetPolicy(api: API, policy: Policy) {
return null; return null;
}); });
} }
export function ensureDomainPolicy(
api: API,
userLoginMustBeDomain: boolean,
validateOrgDomains: boolean,
smtpSenderAddressMatchesInstanceDomain: boolean,
): Cypress.Chainable<number> {
return ensureSetting(
api,
`${api.adminBaseURL}/policies/domain`,
(body: any) => {
const result = {
sequence: parseInt(<string>body.policy?.details?.sequence),
id: body.policy?.details?.resourceOwner,
entity: null,
};
if (
body.policy &&
(body.policy.userLoginMustBeDomain ? body.policy.userLoginMustBeDomain : false) == userLoginMustBeDomain &&
(body.policy.validateOrgDomains ? body.policy.validateOrgDomains : false) == validateOrgDomains &&
(body.policy.smtpSenderAddressMatchesInstanceDomain ? body.policy.smtpSenderAddressMatchesInstanceDomain : false) ==
smtpSenderAddressMatchesInstanceDomain
) {
return { ...result, entity: body.policy };
}
return result;
},
`${api.adminBaseURL}/policies/domain`,
{
userLoginMustBeDomain: userLoginMustBeDomain,
validateOrgDomains: validateOrgDomains,
smtpSenderAddressMatchesInstanceDomain: smtpSenderAddressMatchesInstanceDomain,
},
);
}

View File

@ -474,9 +474,9 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *query.DomainPolicy, lin
} }
if orgIamPolicy.UserLoginMustBeDomain { if orgIamPolicy.UserLoginMustBeDomain {
splittedUsername := strings.Split(username, "@") index := strings.LastIndex(username, "@")
if len(splittedUsername) > 1 { if index > 1 {
username = splittedUsername[0] username = username[:index]
} }
} }

View File

@ -700,12 +700,12 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) bool { func (repo *AuthRequestRepo) checkDomainDiscovery(ctx context.Context, request *domain.AuthRequest, loginName string) bool {
// check if there's a suffix in the loginname // check if there's a suffix in the loginname
split := strings.Split(loginName, "@") index := strings.LastIndex(loginName, "@")
if len(split) < 2 { if index < 0 {
return false return false
} }
// check if the suffix matches a verified domain // check if the suffix matches a verified domain
org, err := repo.Query.OrgByVerifiedDomain(ctx, split[len(split)-1]) org, err := repo.Query.OrgByVerifiedDomain(ctx, loginName[index+1:])
if err != nil { if err != nil {
return false return false
} }

View File

@ -235,12 +235,12 @@ func userValidateDomain(ctx context.Context, a *user.Aggregate, username string,
return nil return nil
} }
usernameSplit := strings.Split(username, "@") index := strings.LastIndex(username, "@")
if len(usernameSplit) != 2 { if index < 0 {
return errors.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid") return nil
} }
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1]) domainCheck := NewOrgDomainVerifiedWriteModel(username[index+1:])
events, err := filter(ctx, domainCheck.Query()) events, err := filter(ctx, domainCheck.Query())
if err != nil { if err != nil {
return err return err
@ -443,11 +443,9 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
human.Username = strings.TrimSpace(human.Username) human.Username = strings.TrimSpace(human.Username)
human.EmailAddress = strings.TrimSpace(human.EmailAddress) human.EmailAddress = strings.TrimSpace(human.EmailAddress)
if !domainPolicy.UserLoginMustBeDomain { if !domainPolicy.UserLoginMustBeDomain {
usernameSplit := strings.Split(human.Username, "@") index := strings.LastIndex(human.Username, "@")
if len(usernameSplit) != 2 { if index > 1 {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid") domainCheck := NewOrgDomainVerifiedWriteModel(human.Username[index+1:])
}
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1])
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil { if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
return nil, nil, err return nil, nil, err
} }
@ -455,6 +453,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername") return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
} }
} }
}
if human.AggregateID == "" { if human.AggregateID == "" {
userID, err := c.idGenerator.Next() userID, err := c.idGenerator.Next()

View File

@ -480,6 +480,224 @@ func TestCommandSide_AddHuman(t *testing.T) {
}, },
}, },
}, },
{
name: "add human, email verified, userLoginMustBeDomain false, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
false,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&userAgg.Aggregate),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &AddHuman{
Username: "username",
Password: "password",
FirstName: "firstname",
LastName: "lastname",
Email: Email{
Address: "email@test.ch",
Verified: true,
},
PreferredLanguage: language.English,
PasswordChangeRequired: true,
},
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.HumanDetails{
ID: "user1",
ObjectDetails: domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
},
{
name: "add human claimed domain, userLoginMustBeDomain false, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
false,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewDomainVerifiedEvent(context.Background(),
&org.NewAggregate("org2").Aggregate,
"test.ch",
),
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &AddHuman{
Username: "username@test.ch",
Password: "password",
FirstName: "firstname",
LastName: "lastname",
Email: Email{
Address: "email@test.ch",
Verified: true,
},
PreferredLanguage: language.English,
PasswordChangeRequired: true,
},
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
err: errors.IsErrorInvalidArgument,
},
},
{
name: "add human domain, userLoginMustBeDomain false, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
false,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewDomainVerifiedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"test.ch",
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&userAgg.Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
func() eventstore.Command {
event := user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username@test.ch",
"firstname",
"lastname",
"",
"firstname lastname",
language.English,
domain.GenderUnspecified,
"email@test.ch",
true,
)
event.AddPasswordData(&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
}, true)
return event
}(),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&userAgg.Aggregate),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username@test.ch", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &AddHuman{
Username: "username@test.ch",
Password: "password",
FirstName: "firstname",
LastName: "lastname",
Email: Email{
Address: "email@test.ch",
Verified: true,
},
PreferredLanguage: language.English,
PasswordChangeRequired: true,
},
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.HumanDetails{
ID: "user1",
ObjectDetails: domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
},
{ {
name: "add human (with phone), ok", name: "add human (with phone), ok",
fields: fields{ fields: fields{
@ -2140,6 +2358,118 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
}, },
}, },
}, },
{
name: "username without @, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
false,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
false,
true,
false,
false,
false,
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Password: &domain.Password{
SecretString: "password",
},
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
Username: "username",
},
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: language.Und,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
State: domain.UserStateInitial,
},
},
},
{ {
name: "add human (with password and initial code), ok", name: "add human (with password and initial code), ok",
fields: fields{ fields: fields{

View File

@ -10,8 +10,9 @@ import (
func (notify Notify) SendDomainClaimed(user *query.NotifyUser, origin, username string) error { func (notify Notify) SendDomainClaimed(user *query.NotifyUser, origin, username string) error {
url := login.LoginLink(origin, user.ResourceOwner) url := login.LoginLink(origin, user.ResourceOwner)
index := strings.LastIndex(user.LastEmail, "@")
args := make(map[string]interface{}) args := make(map[string]interface{})
args["TempUsername"] = username args["TempUsername"] = username
args["Domain"] = strings.Split(user.LastEmail, "@")[1] args["Domain"] = user.LastEmail[index+1:]
return notify(url, args, domain.DomainClaimedMessageType, true) return notify(url, args, domain.DomainClaimedMessageType, true)
} }