This reverts commit 27b4f59a97.
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/200
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.accounts;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
@RestController
|
||||
@@ -188,6 +191,22 @@ public class HsCredentialsController implements CredentialsApi {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Timed("app.credentials.credentials.credentialsUsed")
|
||||
public ResponseEntity<CredentialsResource> credentialsUsed(final UUID credentialsUuid) {
|
||||
context.define();
|
||||
|
||||
val current = credentialsRepo.findByUuid(credentialsUuid).orElseThrow();
|
||||
|
||||
current.setOnboardingToken(null);
|
||||
current.setLastUsed(LocalDateTime.now());
|
||||
|
||||
val saved = credentialsRepo.save(current);
|
||||
val mapped = mapper.map(saved, CredentialsResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
private void validateOnCreate(final HsCredentialsEntity newCredentialsEntity) {
|
||||
validateReferencedPersonToBeRepresentedByLoginUserPerson(newCredentialsEntity);
|
||||
validateNormalUsersOnlyAccessPublicContexts(newCredentialsEntity);
|
||||
@@ -305,6 +324,8 @@ public class HsCredentialsController implements CredentialsApi {
|
||||
}
|
||||
|
||||
final BiConsumer<HsCredentialsEntity, CredentialsResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
ofNullable(entity.getLastUsed()).ifPresent(
|
||||
dt -> resource.setLastUsed(dt.atOffset(ZoneOffset.UTC)));
|
||||
of(entity.getSubject()).ifPresent(
|
||||
subject -> resource.setNickname(subject.getName())
|
||||
);
|
||||
|
||||
@@ -9,7 +9,9 @@ import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
// import net.hostsharing.hsadminng.rbac.RbacSubjectEntity; // Assuming RbacSubjectEntity exists for the FK relationship
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -29,6 +31,8 @@ public class HsCredentialsEntity implements BaseEntity<HsCredentialsEntity>, Str
|
||||
protected static Stringify<HsCredentialsEntity> stringify = stringify(HsCredentialsEntity.class, "credentials")
|
||||
.withProp(HsCredentialsEntity::isActive)
|
||||
.withProp(HsCredentialsEntity::getEmailAddress)
|
||||
.withProp(HsCredentialsEntity::getTotpSecrets)
|
||||
.withProp(HsCredentialsEntity::getPhonePassword)
|
||||
.withProp(HsCredentialsEntity::getSmsNumber)
|
||||
.quotedValues(false);
|
||||
|
||||
@@ -47,6 +51,9 @@ public class HsCredentialsEntity implements BaseEntity<HsCredentialsEntity>, Str
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column
|
||||
private LocalDateTime lastUsed;
|
||||
|
||||
@Column
|
||||
private boolean active;
|
||||
|
||||
@@ -56,6 +63,15 @@ public class HsCredentialsEntity implements BaseEntity<HsCredentialsEntity>, Str
|
||||
@Column
|
||||
private Integer globalGid;
|
||||
|
||||
@Column
|
||||
private String onboardingToken;
|
||||
|
||||
@Column
|
||||
private List<String> totpSecrets;
|
||||
|
||||
@Column
|
||||
private String phonePassword;
|
||||
|
||||
@Column
|
||||
private String emailAddress;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.accounts.generated.api.v1.model.CredentialsPatc
|
||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class HsCredentialsEntityPatcher implements EntityPatcher<CredentialsPatchResource> {
|
||||
|
||||
@@ -22,8 +23,12 @@ public class HsCredentialsEntityPatcher implements EntityPatcher<CredentialsPatc
|
||||
}
|
||||
OptionalFromJson.of(resource.getEmailAddress())
|
||||
.ifPresent(entity::setEmailAddress);
|
||||
Optional.ofNullable(resource.getTotpSecrets())
|
||||
.ifPresent(entity::setTotpSecrets);
|
||||
OptionalFromJson.of(resource.getSmsNumber())
|
||||
.ifPresent(entity::setSmsNumber);
|
||||
OptionalFromJson.of(resource.getPhonePassword())
|
||||
.ifPresent(entity::setPhonePassword);
|
||||
if (resource.getContexts() != null) {
|
||||
contextMapper.syncCredentialsContextEntities(resource.getContexts(), entity.getLoginContexts());
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ paths:
|
||||
|
||||
# Credentials
|
||||
|
||||
/api/hs/accounts/credentials/{credentialsUuid}/used:
|
||||
$ref: "credentials-with-uuid-used.yaml"
|
||||
|
||||
/api/hs/accounts/credentials/{credentialsUuid}:
|
||||
$ref: "credentials-with-uuid.yaml"
|
||||
|
||||
|
||||
@@ -24,6 +24,12 @@ components:
|
||||
nickname:
|
||||
type: string
|
||||
pattern: '^[a-z][a-z0-9]{1,8}-[a-z0-9]{1,10}$' # TODO.spec: pattern for login nickname
|
||||
totpSecrets:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
phonePassword:
|
||||
type: string
|
||||
emailAddress:
|
||||
type: string
|
||||
smsNumber:
|
||||
@@ -34,10 +40,15 @@ components:
|
||||
type: number
|
||||
globalGid:
|
||||
type: number
|
||||
onboardingToken:
|
||||
type: string
|
||||
contexts:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'context-schemas.yaml#/components/schemas/Context'
|
||||
lastUsed:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- uuid
|
||||
- active
|
||||
@@ -47,6 +58,13 @@ components:
|
||||
CredentialsPatch:
|
||||
type: object
|
||||
properties:
|
||||
totpSecrets:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
phonePassword:
|
||||
type: string
|
||||
nullable: true
|
||||
emailAddress:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -70,6 +88,12 @@ components:
|
||||
nickname:
|
||||
type: string
|
||||
pattern: '^[a-z][a-z0-9]{1,8}-[a-z0-9]{1,10}$' # TODO.spec: pattern for login nickname
|
||||
totpSecrets:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
phonePassword:
|
||||
type: string
|
||||
emailAddress:
|
||||
type: string
|
||||
smsNumber:
|
||||
@@ -80,6 +104,8 @@ components:
|
||||
type: number
|
||||
globalGid:
|
||||
type: number
|
||||
onboardingToken:
|
||||
type: string
|
||||
contexts:
|
||||
type: array
|
||||
items:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
post:
|
||||
tags:
|
||||
- credentials
|
||||
description: 'Is called when credentials got used for a login.'
|
||||
operationId: credentialsUsed
|
||||
parameters:
|
||||
- name: credentialsUuid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: 'credentials-schemas.yaml#/components/schemas/Credentials'
|
||||
"401":
|
||||
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: 'error-responses.yaml#/components/responses/Forbidden'
|
||||
@@ -13,9 +13,13 @@ create table hs_accounts.credentials
|
||||
person_uuid uuid not null references hs_office.person(uuid),
|
||||
|
||||
active bool,
|
||||
last_used timestamp,
|
||||
global_uid int unique, -- w/o
|
||||
global_gid int unique, -- w/o
|
||||
onboarding_token text, -- w/o, but can be set to null to invalidate
|
||||
|
||||
totp_secrets text[],
|
||||
phone_password text,
|
||||
email_address text,
|
||||
sms_number text
|
||||
);
|
||||
|
||||
+4
-4
@@ -68,10 +68,10 @@ begin
|
||||
-- call rbac.grantRoleToRole(hs_accounts.context_REFERRER(context_MATRIX_internal), rbac.global_ADMIN());
|
||||
|
||||
-- Add test credentials (linking to assumed rbac.subject UUIDs)
|
||||
INSERT INTO hs_accounts.credentials (uuid, version, person_uuid, active, global_uid, global_gid, email_address, sms_number) VALUES
|
||||
( superuserAlexSubjectUuid, 0, personAlexUuid, true, 1001, 1001, 'alex@example.com', '111-222-3333'),
|
||||
( superuserFranSubjectUuid, 0, personFranUuid, true, 1002, 1002, 'fran@example.com', '444-555-6666'),
|
||||
( userDrewSubjectUuid, 0, personDrewUuid, true, 1003, 1003, 'drew@example.org', '999-888-7777');
|
||||
INSERT INTO hs_accounts.credentials (uuid, version, person_uuid, active, global_uid, global_gid, onboarding_token, totp_secrets, phone_password, email_address, sms_number) VALUES
|
||||
( superuserAlexSubjectUuid, 0, personAlexUuid, true, 1001, 1001, 'token-abc', ARRAY['otp-secret-1a', 'otp-secret-1b'], 'phone-pw-1', 'alex@example.com', '111-222-3333'),
|
||||
( superuserFranSubjectUuid, 0, personFranUuid, true, 1002, 1002, 'token-def', ARRAY['otp-secret-2'], 'phone-pw-2', 'fran@example.com', '444-555-6666'),
|
||||
( userDrewSubjectUuid, 0, personDrewUuid, true, 1003, 1003, 'token-def', ARRAY['otp-secret-3'], 'phone-pw-3', 'drew@example.org', '999-888-7777');
|
||||
|
||||
-- Map credentials to contexts
|
||||
INSERT INTO hs_accounts.context_mapping (credentials_uuid, context_uuid) VALUES
|
||||
|
||||
Reference in New Issue
Block a user