Changes
9 changed files (+281/-1)
-
-
@@ -5,12 +5,14 @@ package projectionimport ( "database/sql" "slices" "google.golang.org/protobuf/proto" "pocka.jp/x/yamori/backend/core/event" "pocka.jp/x/yamori/backend/core/snapshot" workspaceEventsv1 "pocka.jp/x/yamori/proto/go/backend/events/workspace/v1" workspace "pocka.jp/x/yamori/proto/go/backend/projections/workspace/v1" "pocka.jp/x/yamori/proto/go/backend/workspace/v1/types" ) type Users struct {
-
@@ -18,6 +20,8 @@ hasSnapshot booleventSeq *uint64 Projection *workspace.Users permissions map[string]map[types.Permission]struct{} } func NewUsers() *Users {
-
@@ -27,6 +31,7 @@ eventSeq: nil,Projection: &workspace.Users{ Users: []*workspace.Users_User{}, }, permissions: make(map[string]map[types.Permission]struct{}), } }
-
@@ -40,10 +45,22 @@ if payload == nil {return NewUsers(), nil } permissions := make(map[string]map[types.Permission]struct{}) for _, u := range payload.Users { perms := make(map[types.Permission]struct{}) for _, p := range u.Permissions { perms[p] = struct{}{} } permissions[u.GetId()] = perms } return &Users{ hasSnapshot: true, eventSeq: &seq, Projection: payload, permissions: permissions, }, nil }
-
@@ -78,6 +95,7 @@ KeyId: ev.UserCreated.KeyId,IsAdmin: proto.Bool(false), PasswordLogin: nil, }) p.permissions[ev.UserCreated.GetId()] = make(map[types.Permission]struct{}) case *workspaceEventsv1.Event_AdminAccessGranted: id := ev.AdminAccessGranted.GetUserId()
-
@@ -105,6 +123,35 @@ Salt: ev.PasswordLoginConfigured.PasswordSalt,} } } case *workspaceEventsv1.Event_UserPermissionsGranted: id := ev.UserPermissionsGranted.GetUserId() if p.permissions[id] != nil { for _, perm := range ev.UserPermissionsGranted.Permissions { p.permissions[id][perm] = struct{}{} } } case *workspaceEventsv1.Event_UserPermissionsRevoked: id := ev.UserPermissionsRevoked.GetUserId() if p.permissions[id] != nil { for _, perm := range ev.UserPermissionsRevoked.Permissions { delete(p.permissions[id], perm) } } } for _, user := range p.Projection.Users { permissions := p.permissions[user.GetId()] s := make([]types.Permission, 0, len(permissions)) for perm := range permissions { s = append(s, perm) } slices.Sort(s) user.Permissions = slices.Compact(s) } p.eventSeq = &container.Seq
-
-
-
@@ -0,0 +1,79 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package projection_test import ( "database/sql" "io" "log/slog" "testing" eventV1 "pocka.jp/x/yamori/proto/go/backend/events/v1" "pocka.jp/x/yamori/proto/go/backend/workspace/v1/types" "pocka.jp/x/yamori/backend/core" "pocka.jp/x/yamori/backend/core/event" "pocka.jp/x/yamori/backend/core/projection" workspaceEvent "pocka.jp/x/yamori/backend/events/workspace" _ "modernc.org/sqlite" ) func TestUserPermissions(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatal(err) } core, err := core.New(db, logger) if err != nil { t.Fatal(err) } tx, err := core.DB.Begin() if err != nil { t.Fatal(err) } err = event.AppendEvents(tx, []*eventV1.Event{ workspaceEvent.CreateUser("foo", "foo", "Foo", []byte{}), workspaceEvent.GrantPermission("foo", []types.Permission{ types.Permission_PERMISSION_DELETE_REGULAR_USER, types.Permission_PERMISSION_ADD_REGULAR_USER, }), workspaceEvent.RevokePermission("foo", []types.Permission{ types.Permission_PERMISSION_DELETE_REGULAR_USER, types.Permission_PERMISSION_EDIT_WORKSPACE_PROFILE, }), }) if err != nil { t.Fatal(err) } p, err := projection.GetUsers(tx) if err != nil { t.Fatal(err) } if err := event.UpdateProjections(tx, p); err != nil { t.Fatal(err) } for _, u := range p.Projection.Users { if u.GetId() == "foo" { if len(u.Permissions) != 1 { t.Errorf("Expected a slice of length of 1, got length of %d", len(u.Permissions)) } if u.Permissions[0] != types.Permission_PERMISSION_ADD_REGULAR_USER { t.Errorf("Expected ADD_REGULAR_USER, got %v", u.Permissions[0]) } return } } t.Errorf("User foo not created") }
-
-
-
@@ -11,6 +11,7 @@"pocka.jp/x/yamori/backend/crypto" eventV1 "pocka.jp/x/yamori/proto/go/backend/events/v1" workspaceEvent "pocka.jp/x/yamori/proto/go/backend/events/workspace/v1" "pocka.jp/x/yamori/proto/go/backend/workspace/v1/types" ) func GenerateAdminCreationPassword(password string) *eventV1.Event {
-
@@ -107,3 +108,33 @@ },}, } } func GrantPermission(userID string, permissions []types.Permission) *eventV1.Event { return &eventV1.Event{ Event: &eventV1.Event_WorkspaceEvent{ WorkspaceEvent: &workspaceEvent.Event{ Event: &workspaceEvent.Event_UserPermissionsGranted{ UserPermissionsGranted: &workspaceEvent.UserPermissionsGranted{ UserId: proto.String(userID), Permissions: permissions, }, }, }, }, } } func RevokePermission(userID string, permissions []types.Permission) *eventV1.Event { return &eventV1.Event{ Event: &eventV1.Event_WorkspaceEvent{ WorkspaceEvent: &workspaceEvent.Event{ Event: &workspaceEvent.Event_UserPermissionsRevoked{ UserPermissionsRevoked: &workspaceEvent.UserPermissionsRevoked{ UserId: proto.String(userID), Permissions: permissions, }, }, }, }, } }
-
-
-
@@ -13,6 +13,7 @@ "github.com/google/uuid""google.golang.org/protobuf/proto" eventV1 "pocka.jp/x/yamori/proto/go/backend/events/v1" "pocka.jp/x/yamori/proto/go/backend/workspace/v1/types" errorV1 "pocka.jp/x/yamori/proto/go/error/v1" workspaceV2 "pocka.jp/x/yamori/proto/go/workspace/v2"
-
@@ -165,7 +166,6 @@ logger.Error("Failed to append user creation events", "error", err)return createUserSystemError("Database error") } // TODO: 通常ユーザの権限を保存する if req.Msg.GetIsAdmin() { err := event.AppendEvents(tx, []*eventV1.Event{ workspaceEvent.GrantAdminAccess(id.String()),
-
@@ -174,6 +174,53 @@ if err != nil {logger.Error("Failed to append adming grant events", "error", err) return createUserSystemError("Database error") } } else { permissions := make([]types.Permission, 0, 32) if req.Msg.Permissions != nil { if req.Msg.Permissions.GetCanAddUser() { permissions = append(permissions, types.Permission_PERMISSION_ADD_REGULAR_USER) } if req.Msg.Permissions.GetCanDeleteRegularUser() { permissions = append(permissions, types.Permission_PERMISSION_DELETE_REGULAR_USER) } if req.Msg.Permissions.GetCanReadOtherUserProfile() { permissions = append( permissions, types.Permission_PERMISSION_READ_REGULAR_USER_PROFILE, types.Permission_PERMISSION_READ_ADMIN_USER_PROFILE, ) } if req.Msg.Permissions.GetCanUpdateOtherRegularUserProfile() { permissions = append(permissions, types.Permission_PERMISSION_UPDATE_REGULAR_USER_PROFILE) } if req.Msg.Permissions.GetCanUpdateSelfProfile() { permissions = append(permissions, types.Permission_PERMISSION_UPDATE_SELF_PROFILE) } if req.Msg.Permissions.GetCanUpdateOtherRegularUserLoginMethod() { permissions = append( permissions, types.Permission_PERMISSION_UPDATE_REGULAR_USER_LOGIN_METHOD, ) } if req.Msg.Permissions.GetCanUpdateWorkspace() { permissions = append(permissions, types.Permission_PERMISSION_EDIT_WORKSPACE_PROFILE) } } err := event.AppendEvents(tx, []*eventV1.Event{ workspaceEvent.GrantPermission(id.String(), permissions), }) if err != nil { logger.Error("Failed to append a permissions grant event", "error", err) return createUserSystemError("Database error") } } if err := tx.Commit(); err != nil {
-
@@ -183,6 +230,7 @@ }logger.Debug("Created a new user", "id", id.String()) // TODO: 権限を返す return connect.NewResponse(&workspaceV2.CreateUserResponse{ Result: &workspaceV2.CreateUserResponse_Ok{ Ok: &workspaceV2.User{
-
-
-
@@ -12,6 +12,8 @@ import "yamori/backend/events/workspace/v1/admin_creation_password_generated.proto";import "yamori/backend/events/workspace/v1/login_jwt_secret_configured.proto"; import "yamori/backend/events/workspace/v1/password_login_configured.proto"; import "yamori/backend/events/workspace/v1/user_created.proto"; import "yamori/backend/events/workspace/v1/user_permissions_granted.proto"; import "yamori/backend/events/workspace/v1/user_permissions_revoked.proto"; import "yamori/backend/events/workspace/v1/workspace_display_name_set.proto"; option go_package = "pocka.jp/x/yamori/proto/go/backend/events/workspace/v1";
-
@@ -26,5 +28,7 @@ AdminCreationPasswordGenerated admin_creation_password_generated = 5;AdminCreationPasswordExpired admin_creation_password_expired = 6; LoginJwtSecretConfigured login_jwt_secret_configured = 7; WorkspaceDisplayNameSet workspace_display_name_set = 8; UserPermissionsGranted user_permissions_granted = 9; UserPermissionsRevoked user_permissions_revoked = 10; } }
-
-
-
@@ -0,0 +1,16 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only edition = "2023"; package yamori.backend.events.workspace.v1; import "yamori/backend/workspace/v1/types/v1/permission.proto"; option go_package = "pocka.jp/x/yamori/proto/go/backend/events/workspace/v1"; message UserPermissionsGranted { string user_id = 1; repeated yamori.backend.workspace.v1.types.v1.Permission permissions = 2; }
-
-
-
@@ -0,0 +1,16 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only edition = "2023"; package yamori.backend.events.workspace.v1; import "yamori/backend/workspace/v1/types/v1/permission.proto"; option go_package = "pocka.jp/x/yamori/proto/go/backend/events/workspace/v1"; message UserPermissionsRevoked { string user_id = 1; repeated yamori.backend.workspace.v1.types.v1.Permission permissions = 2; }
-
-
-
@@ -5,6 +5,8 @@ edition = "2023";package yamori.backend.projections.workspace.v1; import "yamori/backend/workspace/v1/types/v1/permission.proto"; option go_package = "pocka.jp/x/yamori/proto/go/backend/projections/workspace/v1"; // ユーザの一覧。
-
@@ -25,6 +27,7 @@ string display_name = 3;PasswordLogin password_login = 4; bool is_admin = 5; bytes key_id = 6; repeated yamori.backend.workspace.v1.types.v1.Permission permissions = 7; } repeated User users = 1;
-
-
-
@@ -0,0 +1,36 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only edition = "2023"; package yamori.backend.workspace.v1.types.v1; option go_package = "pocka.jp/x/yamori/proto/go/backend/workspace/v1/types"; enum Permission { PERMISSION_UNKNOWN = 0; // 通常ユーザの追加権限。 PERMISSION_ADD_REGULAR_USER = 1; // 自分以外の通常ユーザの削除権限。 PERMISSION_DELETE_REGULAR_USER = 2; // 自分以外の通常ユーザの基本情報閲覧権限。 PERMISSION_READ_REGULAR_USER_PROFILE = 3; // 自分以外の管理者ユーザの基本情報閲覧権限。 PERMISSION_READ_ADMIN_USER_PROFILE = 4; // 自分以外の通常ユーザの基本情報変更権限。 PERMISSION_UPDATE_REGULAR_USER_PROFILE = 5; // 自身の基本情報変更権限。 PERMISSION_UPDATE_SELF_PROFILE = 6; // 自分以外の通常ユーザのログイン手段変更権限。 PERMISSION_UPDATE_REGULAR_USER_LOGIN_METHOD = 7; // ワークスペースの設定の変更権限。 PERMISSION_EDIT_WORKSPACE_PROFILE = 8; }
-