Add typed account and password settings
- Add admin account list, create, role, delete, and password workflows - Execute identity-checked mutations through the existing dispatcher - Bound queued credential lifetime and wipe transient secrets - Add explicit password generation with saved-value acknowledgement - Handle self-revocation and uncertain outcomes without automatic retries - Register optional account routes without disrupting terminal transports - Expand host regressions and document contracts and pending target checks Validated host suites and pio run; hardware validation remains pending.
This commit is contained in:
@@ -39,8 +39,109 @@ static void unchanged(const stored_database_t *before)
|
||||
assert(all_zero(s_candidate,sizeof(*s_candidate)));
|
||||
assert(!locks);
|
||||
}
|
||||
static void typed_account_tests(void)
|
||||
{
|
||||
reset(); user_database_accounts_t list;
|
||||
assert(user_database_get_accounts(&list)==ESP_OK && last_wait==0 && list.count==3);
|
||||
assert(!strcmp(list.users[1].username,"other"));
|
||||
user_database_account_t other=list.users[1], admin=list.users[0];
|
||||
snapshot_busy=true; memset(&list,0xff,sizeof(list));
|
||||
assert(user_database_get_accounts(&list)==ESP_ERR_TIMEOUT && all_zero(&list,sizeof(list)));
|
||||
snapshot_busy=false;
|
||||
assert(user_database_delete_current(&admin)==ESP_ERR_INVALID_STATE);
|
||||
assert(user_database_set_role_current(&admin,USER_ROLE_USER)==ESP_ERR_INVALID_STATE);
|
||||
assert(!writes && !commits);
|
||||
assert(user_database_set_role_current(&other,USER_ROLE_ADMIN)==ESP_OK);
|
||||
unsigned saved=commits;
|
||||
assert(user_database_delete_current(&other)==ESP_ERR_NOT_FOUND && commits==saved);
|
||||
assert(user_database_set_role_current(&other,USER_ROLE_USER)==ESP_ERR_NOT_FOUND);
|
||||
assert(user_database_get_accounts(&list)==ESP_OK); other=list.users[1];
|
||||
for (fail_stage=1;fail_stage<=3;++fail_stage) {
|
||||
stored_database_t before=s_database;
|
||||
assert(user_database_delete_current(&other)==ESP_FAIL); unchanged(&before);
|
||||
assert(user_database_set_role_current(&other,USER_ROLE_USER)==ESP_FAIL); unchanged(&before);
|
||||
}
|
||||
fail_stage=0; assert(user_database_delete_current(&other)==ESP_OK);
|
||||
assert(user_database_create((const uint8_t *)"other",5,USER_ROLE_USER,(const uint8_t *)"test-password",13)==ESP_OK);
|
||||
assert(user_database_delete_current(&other)==ESP_ERR_NOT_FOUND);
|
||||
assert(user_database_set_role_current(&other,USER_ROLE_ADMIN)==ESP_ERR_NOT_FOUND);
|
||||
assert(user_database_get_accounts(&list)==ESP_OK); other=list.users[1];
|
||||
assert(user_database_delete_current(&other)==ESP_OK);
|
||||
assert(all_zero(s_candidate,sizeof(*s_candidate)) && !locks);
|
||||
s_initialized=false; memset(&list,0xff,sizeof(list));
|
||||
assert(user_database_get_accounts(&list)==ESP_ERR_INVALID_STATE && all_zero(&list,sizeof(list)));
|
||||
assert(user_database_delete_current(NULL)==ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
static void typed_password_tests(void)
|
||||
{
|
||||
reset(); user_database_accounts_t list;
|
||||
assert(user_database_get_accounts(&list)==ESP_OK);
|
||||
user_database_account_t other=list.users[1], admin=list.users[0];
|
||||
const uint8_t password[]="quote\"slash\\ space";
|
||||
for (unsigned stage=1;stage<=5;++stage) {
|
||||
fail_stage=stage; stored_database_t before=s_database;
|
||||
assert(user_database_set_password_current(&other,password,sizeof(password)-1)==ESP_FAIL);
|
||||
unchanged(&before);
|
||||
}
|
||||
fail_stage=0; writes=commits=0;
|
||||
assert(user_database_set_password_current(&other,password,sizeof(password)-1)==ESP_OK);
|
||||
assert(writes==1 && commits==1 && s_database.users[1].auth_generation==other.auth_generation+1);
|
||||
assert(all_zero(s_candidate,sizeof(*s_candidate)));
|
||||
stored_database_t before=s_database; unsigned rng=random_calls;
|
||||
assert(user_database_set_password_current(&other,password,sizeof(password)-1)==ESP_ERR_NOT_FOUND);
|
||||
unchanged(&before); assert(writes==1 && commits==1 && random_calls==rng);
|
||||
assert(user_database_get_accounts(&list)==ESP_OK); other=list.users[1];
|
||||
assert(user_database_delete_current(&other)==ESP_OK);
|
||||
assert(user_database_create((const uint8_t *)"other",5,USER_ROLE_USER,password,sizeof(password)-1)==ESP_OK);
|
||||
before=s_database; rng=random_calls;
|
||||
assert(user_database_set_password_current(&other,password,sizeof(password)-1)==ESP_ERR_NOT_FOUND);
|
||||
unchanged(&before); assert(random_calls==rng);
|
||||
assert(user_database_set_password_current(NULL,password,sizeof(password)-1)==ESP_ERR_INVALID_ARG);
|
||||
other.user_id=0;
|
||||
assert(user_database_set_password_current(&other,password,sizeof(password)-1)==ESP_ERR_NOT_FOUND);
|
||||
memset(other.username,'x',sizeof(other.username));
|
||||
assert(user_database_set_password_current(&other,password,sizeof(password)-1)==ESP_ERR_INVALID_ARG);
|
||||
assert(user_database_set_password_current(&admin,(const uint8_t *)"short",5)==ESP_ERR_INVALID_ARG);
|
||||
/* Own password is allowed even for the last administrator; old principal is stale. */
|
||||
assert(user_database_set_password_current(&admin,password,sizeof(password)-1)==ESP_OK);
|
||||
bool current=true; assert(user_database_principal_is_current(&actor,¤t)==ESP_OK && !current);
|
||||
assert(s_database.admin_count==1);
|
||||
/* With a second admin, canonical self role/delete invariants allow both. */
|
||||
assert(user_database_set_role((const uint8_t *)"other",5,USER_ROLE_ADMIN)==ESP_OK);
|
||||
assert(user_database_get_accounts(&list)==ESP_OK); admin=list.users[0];
|
||||
assert(user_database_set_role_current(&admin,USER_ROLE_USER)==ESP_OK);
|
||||
assert(user_database_get_accounts(&list)==ESP_OK); admin=list.users[0];
|
||||
assert(user_database_delete_current(&admin)==ESP_OK);
|
||||
reset(); before=s_database; rng=random_calls;
|
||||
assert(user_database_create((const uint8_t *)"other",5,USER_ROLE_ADMIN,password,sizeof(password)-1)==ESP_ERR_INVALID_STATE);
|
||||
unchanged(&before); assert(!writes && !commits && rng==random_calls);
|
||||
for (unsigned i=3;i<USER_DATABASE_MAX_USERS;++i) {
|
||||
char name[17]; snprintf(name,sizeof(name),"account%u",i);
|
||||
assert(user_database_create((const uint8_t *)name,strlen(name),USER_ROLE_USER,password,sizeof(password)-1)==ESP_OK);
|
||||
}
|
||||
before=s_database; rng=random_calls; unsigned saved=commits;
|
||||
assert(user_database_create((const uint8_t *)"extra",5,USER_ROLE_USER,password,sizeof(password)-1)==ESP_ERR_NO_MEM);
|
||||
unchanged(&before); assert(commits==saved && rng==random_calls);
|
||||
/* RNG-only helper is independent of initialized storage and leaves all DB state alone. */
|
||||
s_initialized=false; s_mutex=NULL;
|
||||
for (unsigned mode=0;mode<2;++mode) {
|
||||
user_database_generated_password_t generated; memset(&generated,0xa5,sizeof(generated));
|
||||
fail_stage=mode ? 4 : 0;
|
||||
assert(user_database_generate_password_value(&generated)==(mode ? ESP_FAIL : ESP_OK));
|
||||
if (mode) assert(all_zero(&generated,sizeof(generated)));
|
||||
else {
|
||||
assert(generated.password_length==24 && strlen((const char *)generated.password)==24);
|
||||
for (size_t i=0;i<24;++i) assert(strchr((const char *)s_generated_alphabet,generated.password[i]));
|
||||
}
|
||||
assert(!memcmp(&before,&s_database,sizeof(before)) && commits==saved && !locks);
|
||||
secure_wipe(&generated,sizeof(generated));
|
||||
}
|
||||
assert(user_database_generate_password_value(NULL)==ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
typed_account_tests();
|
||||
typed_password_tests();
|
||||
const char *supported[]={
|
||||
"user add fresh user", "user add fresh admin", "user password other",
|
||||
"user delete other --force", "user role other admin --force",
|
||||
|
||||
Reference in New Issue
Block a user