From 1eb96e906dce2ee80b622f1f7215e6ceea09f565 Mon Sep 17 00:00:00 2001 From: LandaMm Date: Wed, 25 Jun 2025 11:55:27 +0200 Subject: [PATCH] feat: create system roles --- internal/repository/roles.sql.go | 152 +++++++++++++++++++++++++++++++ internal/user/permissions.go | 142 +++++++++++++++++++++++++++++ queries/roles.sql | 65 +++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 internal/repository/roles.sql.go create mode 100644 queries/roles.sql diff --git a/internal/repository/roles.sql.go b/internal/repository/roles.sql.go new file mode 100644 index 0000000..61ba999 --- /dev/null +++ b/internal/repository/roles.sql.go @@ -0,0 +1,152 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: roles.sql + +package repository + +import ( + "context" + + "github.com/google/uuid" +) + +const addPermissionsToRoleByKey = `-- name: AddPermissionsToRoleByKey :exec +INSERT INTO role_permissions (role_id, permission_id) +SELECT + $1, + p.id +FROM + permissions p +JOIN + unnest($2::text[]) AS key_str + ON key_str = p.scope || '_' || p.name +` + +type AddPermissionsToRoleByKeyParams struct { + RoleID uuid.UUID `json:"role_id"` + PermissionKeys []string `json:"permission_keys"` +} + +func (q *Queries) AddPermissionsToRoleByKey(ctx context.Context, arg AddPermissionsToRoleByKeyParams) error { + _, err := q.db.Exec(ctx, addPermissionsToRoleByKey, arg.RoleID, arg.PermissionKeys) + return err +} + +const createRole = `-- name: CreateRole :one +INSERT INTO roles (name, scope, description) +VALUES ($1, $2, $3) +RETURNING id, name, scope, description +` + +type CreateRoleParams struct { + Name string `json:"name"` + Scope string `json:"scope"` + Description *string `json:"description"` +} + +func (q *Queries) CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error) { + row := q.db.QueryRow(ctx, createRole, arg.Name, arg.Scope, arg.Description) + var i Role + err := row.Scan( + &i.ID, + &i.Name, + &i.Scope, + &i.Description, + ) + return i, err +} + +const findRole = `-- name: FindRole :one +SELECT id, name, scope, description +FROM roles +WHERE scope = $1 AND name = $2 +` + +type FindRoleParams struct { + Scope string `json:"scope"` + Name string `json:"name"` +} + +func (q *Queries) FindRole(ctx context.Context, arg FindRoleParams) (Role, error) { + row := q.db.QueryRow(ctx, findRole, arg.Scope, arg.Name) + var i Role + err := row.Scan( + &i.ID, + &i.Name, + &i.Scope, + &i.Description, + ) + return i, err +} + +const getRolesGroupedWithPermissions = `-- name: GetRolesGroupedWithPermissions :many +SELECT + r.scope, + json_agg ( + json_build_object ( + 'id', + r.id, + 'name', + r.name, + 'description', + r.description, + 'permissions', + COALESCE(p_list.permissions, '[]') + ) + ORDER BY + r.name + ) AS roles +FROM + roles r + LEFT JOIN ( + SELECT + rp.role_id, + json_agg ( + json_build_object ( + 'id', + p.id, + 'name', + p.name, + 'scope', + p.scope, + 'description', + p.description + ) + ORDER BY + p.name + ) AS permissions + FROM + role_permissions rp + JOIN permissions p ON p.id = rp.permission_id + GROUP BY + rp.role_id + ) p_list ON p_list.role_id = r.id +GROUP BY + r.scope +` + +type GetRolesGroupedWithPermissionsRow struct { + Scope string `json:"scope"` + Roles []byte `json:"roles"` +} + +func (q *Queries) GetRolesGroupedWithPermissions(ctx context.Context) ([]GetRolesGroupedWithPermissionsRow, error) { + rows, err := q.db.Query(ctx, getRolesGroupedWithPermissions) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetRolesGroupedWithPermissionsRow + for rows.Next() { + var i GetRolesGroupedWithPermissionsRow + if err := rows.Scan(&i.Scope, &i.Roles); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/user/permissions.go b/internal/user/permissions.go index 6f79bf5..a0af72f 100644 --- a/internal/user/permissions.go +++ b/internal/user/permissions.go @@ -2,6 +2,7 @@ package user import ( "context" + "fmt" "log" "gitea.local/admin/hspguard/internal/repository" @@ -11,6 +12,11 @@ func String(s string) *string { return &s } +type RolePermissions struct { + Permissions []string `json:"permissions"` + repository.Role +} + var ( SYSTEM_SCOPE string = "system" SYSTEM_PERMISSIONS []repository.Permission = []repository.Permission{ @@ -46,6 +52,111 @@ var ( Name: "revoke_sessions", Description: String("Allow users to revoke their active sessions"), }, + { + Name: "view_api_services", + Description: String("Allow users to view API Services (for admin)"), + }, + { + Name: "add_api_services", + Description: String("Allow users to register new API Services (for admin)"), + }, + { + Name: "edit_api_services", + Description: String("Allow users to edit API Services (for admin)"), + }, + { + Name: "remove_api_services", + Description: String("Allow users to remove API Services (for admin)"), + }, + { + Name: "view_users", + Description: String("Allow users to view other users (for admin)"), + }, + { + Name: "add_users", + Description: String("Allow users to create new users (for admin)"), + }, + // TODO: block, delete users + { + Name: "view_user_sessions", + Description: String("Allow users to view user sessions (for admin)"), + }, + { + Name: "revoke_user_sessions", + Description: String("Allow users to revoke user sessions (for admin)"), + }, + { + Name: "view_service_sessions", + Description: String("Allow users to view service sessions (for admin)"), + }, + { + Name: "revoke_service_sessions", + Description: String("Allow users to revoke service sessions (for admin)"), + }, + { + Name: "view_permissions", + Description: String("Allow users to view all permissions (for admin)"), + }, + { + Name: "view_roles_groups", + Description: String("Allow users to view roles & groups (for admin)"), + }, + } + SYSTEM_ROLES []RolePermissions = []RolePermissions{ + { + Permissions: []string{ + "system_log_into_guard", + "system_register", + "system_edit_profile", + "system_recover_credentials", + "system_verify_profile", + "system_access_home_services", + "system_view_sessions", + "system_revoke_sessions", + "system_view_api_services", + "system_add_api_services", + "system_edit_api_services", + "system_remove_api_services", + "system_view_users", + "system_add_users", + "system_view_user_sessions", + "system_revoke_user_sessions", + "system_view_service_sessions", + "system_revoke_service_sessions", + "system_view_permissions", + "system_view_roles_groups", + }, + Role: repository.Role{ + Name: "admin", + Description: String("User with full power"), + }, + }, + { + Permissions: []string{ + "system_log_into_guard", + "system_register", + "system_edit_profile", + "system_recover_credentials", + "system_verify_profile", + "system_access_home_services", + "system_view_sessions", + "system_revoke_sessions", + }, + Role: repository.Role{ + Name: "family_member", + Description: String("User that is able to use home services"), + }, + }, + { + Permissions: []string{ + "system_log_into_guard", + "system_register", + }, + Role: repository.Role{ + Name: "guest", + Description: String("New user that needs approve for everything from admin"), + }, + }, } ) @@ -67,4 +178,35 @@ func EnsureSystemPermissions(ctx context.Context, repo *repository.Queries) { } } } + + for _, role := range SYSTEM_ROLES { + found, err := repo.FindRole(ctx, repository.FindRoleParams{ + Scope: SYSTEM_SCOPE, + Name: role.Name, + }) + if err != nil { + log.Printf("INFO: Create new SYSTEM role '%s'\n", role.Name) + found, err = repo.CreateRole(ctx, repository.CreateRoleParams{ + Name: role.Name, + Scope: SYSTEM_SCOPE, + Description: role.Description, + }) + if err != nil { + log.Fatalf("ERR: Failed to create SYSTEM role '%s': %v\n", role.Name, err) + } + } + + var mappedPerms []string + + for _, perm := range role.Permissions { + mappedPerms = append(mappedPerms, fmt.Sprintf("%s_%s", SYSTEM_SCOPE, perm)) + } + + if err := repo.AddPermissionsToRoleByKey(ctx, repository.AddPermissionsToRoleByKeyParams{ + RoleID: found.ID, + PermissionKeys: mappedPerms, + }); err != nil { + log.Fatalf("ERR: Failed to assign required permissions to SYSTEM role %s: %v\n", found.Name, err) + } + } } diff --git a/queries/roles.sql b/queries/roles.sql new file mode 100644 index 0000000..201190e --- /dev/null +++ b/queries/roles.sql @@ -0,0 +1,65 @@ +-- name: FindRole :one +SELECT * +FROM roles +WHERE scope = $1 AND name = $2; + +-- name: GetRolesGroupedWithPermissions :many +SELECT + r.scope, + json_agg ( + json_build_object ( + 'id', + r.id, + 'name', + r.name, + 'description', + r.description, + 'permissions', + COALESCE(p_list.permissions, '[]') + ) + ORDER BY + r.name + ) AS roles +FROM + roles r + LEFT JOIN ( + SELECT + rp.role_id, + json_agg ( + json_build_object ( + 'id', + p.id, + 'name', + p.name, + 'scope', + p.scope, + 'description', + p.description + ) + ORDER BY + p.name + ) AS permissions + FROM + role_permissions rp + JOIN permissions p ON p.id = rp.permission_id + GROUP BY + rp.role_id + ) p_list ON p_list.role_id = r.id +GROUP BY + r.scope; + +-- name: CreateRole :one +INSERT INTO roles (name, scope, description) +VALUES ($1, $2, $3) +RETURNING *; + +-- name: AddPermissionsToRoleByKey :exec +INSERT INTO role_permissions (role_id, permission_id) +SELECT + sqlc.arg(role_id), + p.id +FROM + permissions p +JOIN + unnest(sqlc.arg(permission_keys)::text[]) AS key_str + ON key_str = p.scope || '_' || p.name;