mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
feat: user roles
This commit is contained in:
+11
-3
@@ -19,9 +19,17 @@ type Identity struct {
|
||||
}
|
||||
|
||||
type IAMPolicy struct {
|
||||
Subs []string `json:"subs,omitempty"`
|
||||
Emails []string `json:"emails,omitempty"`
|
||||
Filters []string `json:"filters,omitempty"`
|
||||
Subs []string `json:"subs,omitempty"`
|
||||
Emails []string `json:"emails,omitempty"`
|
||||
Filters []string `json:"filters,omitempty"`
|
||||
Roles map[string]UserRole `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func (i *IAMPolicy) GetRole(user User) UserRole {
|
||||
if val, ok := i.Roles[user.Name]; ok {
|
||||
return val
|
||||
}
|
||||
return UserRoleMember
|
||||
}
|
||||
|
||||
func (i *IAMPolicy) EvaluatePolicy(identity *Identity) (bool, error) {
|
||||
|
||||
@@ -23,6 +23,18 @@ const (
|
||||
UserTypePerson UserType = "person"
|
||||
)
|
||||
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
UserRoleNone UserRole = ""
|
||||
UserRoleMember UserRole = "member"
|
||||
UserRoleAdmin UserRole = "admin"
|
||||
)
|
||||
|
||||
func (s UserRole) IsAdmin() bool {
|
||||
return s == UserRoleAdmin
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID uint64 `gorm:"primary_key;autoIncrement:false"`
|
||||
Name string
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func (s *Service) GetACLPolicy(ctx context.Context, req *connect.Request[api.GetACLPolicyRequest]) (*connect.Response[api.GetACLPolicyResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Service) GetACLPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
func (s *Service) SetACLPolicy(ctx context.Context, req *connect.Request[api.SetACLPolicyRequest]) (*connect.Response[api.SetACLPolicyResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ func (s *Service) GetAuthKey(ctx context.Context, req *connect.Request[api.GetAu
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("auth key not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() && !principal.UserMatches(key.UserID) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(key.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func mapAuthKeysToApi(authKeys []domain.AuthKey) []*api.AuthKey {
|
||||
|
||||
func (s *Service) ListAuthKeys(ctx context.Context, req *connect.Request[api.ListAuthKeysRequest]) (*connect.Response[api.ListAuthKeysResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ func (s *Service) ListAuthKeys(ctx context.Context, req *connect.Request[api.Lis
|
||||
|
||||
func (s *Service) CreateAuthKey(ctx context.Context, req *connect.Request[api.CreateAuthKeyRequest]) (*connect.Response[api.CreateAuthKeyResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func (s *Service) DeleteAuthKey(ctx context.Context, req *connect.Request[api.De
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("auth key not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() && !principal.UserMatches(key.UserID) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(key.UserID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func (s *Service) GetDNSConfig(ctx context.Context, req *connect.Request[api.GetDNSConfigRequest]) (*connect.Response[api.GetDNSConfigResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *Service) GetDNSConfig(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
func (s *Service) SetDNSConfig(ctx context.Context, req *connect.Request[api.SetDNSConfigRequest]) (*connect.Response[api.SetDNSConfigResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
|
||||
+20
-2
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (s *Service) GetIAMPolicy(ctx context.Context, req *connect.Request[api.GetIAMPolicyRequest]) (*connect.Response[api.GetIAMPolicyResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func (s *Service) GetIAMPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
Subs: tailnet.IAMPolicy.Subs,
|
||||
Emails: tailnet.IAMPolicy.Emails,
|
||||
Filters: tailnet.IAMPolicy.Filters,
|
||||
Roles: domainRolesMapToApiRolesMap(tailnet.IAMPolicy.Roles),
|
||||
}
|
||||
|
||||
return connect.NewResponse(&api.GetIAMPolicyResponse{Policy: policy}), nil
|
||||
@@ -34,7 +35,7 @@ func (s *Service) GetIAMPolicy(ctx context.Context, req *connect.Request[api.Get
|
||||
|
||||
func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.SetIAMPolicyRequest]) (*connect.Response[api.SetIAMPolicyResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
Subs: req.Msg.Policy.Subs,
|
||||
Emails: req.Msg.Policy.Emails,
|
||||
Filters: req.Msg.Policy.Filters,
|
||||
Roles: apiRolesMapToDomainRolesMap(req.Msg.Policy.Roles),
|
||||
}
|
||||
|
||||
if err := s.repository.SaveTailnet(ctx, tailnet); err != nil {
|
||||
@@ -58,3 +60,19 @@ func (s *Service) SetIAMPolicy(ctx context.Context, req *connect.Request[api.Set
|
||||
|
||||
return connect.NewResponse(&api.SetIAMPolicyResponse{}), nil
|
||||
}
|
||||
|
||||
func apiRolesMapToDomainRolesMap(values map[string]string) map[string]domain.UserRole {
|
||||
var result = map[string]domain.UserRole{}
|
||||
for k, v := range values {
|
||||
result[k] = domain.UserRole(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func domainRolesMapToApiRolesMap(values map[string]domain.UserRole) map[string]string {
|
||||
var result = map[string]string{}
|
||||
for k, v := range values {
|
||||
result[k] = string(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -21,13 +21,18 @@ const (
|
||||
type Principal struct {
|
||||
SystemRole domain.SystemRole
|
||||
User *domain.User
|
||||
UserRole domain.UserRole
|
||||
}
|
||||
|
||||
func (p Principal) IsSystemAdmin() bool {
|
||||
return p.SystemRole.IsAdmin()
|
||||
}
|
||||
|
||||
func (p Principal) TailnetMatches(tailnetID uint64) bool {
|
||||
func (p Principal) IsTailnetAdmin(tailnetID uint64) bool {
|
||||
return p.User.TailnetID == tailnetID && p.UserRole.IsAdmin()
|
||||
}
|
||||
|
||||
func (p Principal) IsTailnetMember(tailnetID uint64) bool {
|
||||
return p.User.TailnetID == tailnetID
|
||||
}
|
||||
|
||||
@@ -38,7 +43,7 @@ func (p Principal) UserMatches(userID uint64) bool {
|
||||
func CurrentPrincipal(ctx context.Context) Principal {
|
||||
p := ctx.Value(principalKey)
|
||||
if p == nil {
|
||||
return Principal{SystemRole: domain.SystemRoleNone}
|
||||
return Principal{SystemRole: domain.SystemRoleNone, UserRole: domain.UserRoleNone}
|
||||
}
|
||||
return p.(Principal)
|
||||
}
|
||||
@@ -81,5 +86,9 @@ func exchangeToken(ctx context.Context, systemAdminKey key.ServerPrivate, reposi
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Principal{User: &apiKey.User, SystemRole: domain.SystemRoleNone}
|
||||
user := apiKey.User
|
||||
tailnet := apiKey.Tailnet
|
||||
role := tailnet.IAMPolicy.GetRole(user)
|
||||
|
||||
return &Principal{User: &apiKey.User, SystemRole: domain.SystemRoleNone, UserRole: role}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
func (s *Service) ListMachines(ctx context.Context, req *connect.Request[api.ListMachinesRequest]) (*connect.Response[api.ListMachinesResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.TailnetId) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func (s *Service) DeleteMachine(ctx context.Context, req *connect.Request[api.De
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("machine not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() && !principal.UserMatches(m.UserID) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(m.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func (s *Service) ExpireMachine(ctx context.Context, req *connect.Request[api.Ex
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("machine not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() && !principal.UserMatches(m.UserID) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(m.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (s *Service) GetMachineRoutes(ctx context.Context, req *connect.Request[api
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("machine not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(m.TailnetID) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(m.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func (s *Service) SetMachineRoutes(ctx context.Context, req *connect.Request[api
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("machine not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(m.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ func (s *Service) SetMachineKeyExpiry(ctx context.Context, req *connect.Request[
|
||||
return nil, connect.NewError(connect.CodeNotFound, errors.New("machine not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(m.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Service) CreateTailnet(ctx context.Context, req *connect.Request[api.Cr
|
||||
|
||||
func (s *Service) GetTailnet(ctx context.Context, req *connect.Request[api.GetTailnetRequest]) (*connect.Response[api.GetTailnetResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.Id) {
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(req.Msg.Id) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func (s *Service) ListTailnets(ctx context.Context, req *connect.Request[api.Lis
|
||||
|
||||
func (s *Service) DeleteTailnet(ctx context.Context, req *connect.Request[api.DeleteTailnetRequest]) (*connect.Response[api.DeleteTailnetResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
if !principal.IsSystemAdmin() && !principal.TailnetMatches(req.Msg.TailnetId) {
|
||||
if !principal.IsSystemAdmin() {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("permission denied"))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user