diff --git a/api/org-unit-role.pb.go b/api/org-unit-role.pb.go index 91aaea6..d285f38 100644 --- a/api/org-unit-role.pb.go +++ b/api/org-unit-role.pb.go @@ -26,6 +26,126 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ResourceMatchCriteriaDefs_Criteria int32 + +const ( + // Unspecified/default (treated as deny for security) + ResourceMatchCriteriaDefs_Unspecified ResourceMatchCriteriaDefs_Criteria = 0 + // Matches all resources (wildcard) + ResourceMatchCriteriaDefs_Any ResourceMatchCriteriaDefs_Criteria = 1 + // Exact resource name match + ResourceMatchCriteriaDefs_Exact ResourceMatchCriteriaDefs_Criteria = 2 + // Resource name starts with the pattern + ResourceMatchCriteriaDefs_Prefix ResourceMatchCriteriaDefs_Criteria = 3 + // Resource name ends with the pattern + ResourceMatchCriteriaDefs_Suffix ResourceMatchCriteriaDefs_Criteria = 4 + // Resource name matches the regex pattern + ResourceMatchCriteriaDefs_Regex ResourceMatchCriteriaDefs_Criteria = 5 +) + +// Enum value maps for ResourceMatchCriteriaDefs_Criteria. +var ( + ResourceMatchCriteriaDefs_Criteria_name = map[int32]string{ + 0: "Unspecified", + 1: "Any", + 2: "Exact", + 3: "Prefix", + 4: "Suffix", + 5: "Regex", + } + ResourceMatchCriteriaDefs_Criteria_value = map[string]int32{ + "Unspecified": 0, + "Any": 1, + "Exact": 2, + "Prefix": 3, + "Suffix": 4, + "Regex": 5, + } +) + +func (x ResourceMatchCriteriaDefs_Criteria) Enum() *ResourceMatchCriteriaDefs_Criteria { + p := new(ResourceMatchCriteriaDefs_Criteria) + *p = x + return p +} + +func (x ResourceMatchCriteriaDefs_Criteria) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResourceMatchCriteriaDefs_Criteria) Descriptor() protoreflect.EnumDescriptor { + return file_org_unit_role_proto_enumTypes[0].Descriptor() +} + +func (ResourceMatchCriteriaDefs_Criteria) Type() protoreflect.EnumType { + return &file_org_unit_role_proto_enumTypes[0] +} + +func (x ResourceMatchCriteriaDefs_Criteria) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResourceMatchCriteriaDefs_Criteria.Descriptor instead. +func (ResourceMatchCriteriaDefs_Criteria) EnumDescriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{3, 0} +} + +type RolePermissionActionDefs_Action int32 + +const ( + // Unspecified action (invalid, will be denied) + RolePermissionActionDefs_Unspecified RolePermissionActionDefs_Action = 0 + // Allow the specified verbs on the resource + RolePermissionActionDefs_Allow RolePermissionActionDefs_Action = 1 + // Deny the specified verbs on the resource (takes precedence over Allow) + RolePermissionActionDefs_Deny RolePermissionActionDefs_Action = 2 + // Log the specified verbs on the resource (allows access but logs for audit) + RolePermissionActionDefs_Log RolePermissionActionDefs_Action = 3 +) + +// Enum value maps for RolePermissionActionDefs_Action. +var ( + RolePermissionActionDefs_Action_name = map[int32]string{ + 0: "Unspecified", + 1: "Allow", + 2: "Deny", + 3: "Log", + } + RolePermissionActionDefs_Action_value = map[string]int32{ + "Unspecified": 0, + "Allow": 1, + "Deny": 2, + "Log": 3, + } +) + +func (x RolePermissionActionDefs_Action) Enum() *RolePermissionActionDefs_Action { + p := new(RolePermissionActionDefs_Action) + *p = x + return p +} + +func (x RolePermissionActionDefs_Action) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RolePermissionActionDefs_Action) Descriptor() protoreflect.EnumDescriptor { + return file_org_unit_role_proto_enumTypes[1].Descriptor() +} + +func (RolePermissionActionDefs_Action) Type() protoreflect.EnumType { + return &file_org_unit_role_proto_enumTypes[1] +} + +func (x RolePermissionActionDefs_Action) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RolePermissionActionDefs_Action.Descriptor instead. +func (RolePermissionActionDefs_Action) EnumDescriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{4, 0} +} + // org unit roles list request type OrgUnitRolesListReq struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -77,7 +197,13 @@ type OrgUnitRolesListEntry struct { // role name, unique in an Org Unit Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // role description, provding details about the role - Desc string `protobuf:"bytes,2,opt,name=desc,proto3" json:"desc,omitempty"` + Desc string `protobuf:"bytes,2,opt,name=desc,proto3" json:"desc,omitempty"` + // role type: "built-in" for system roles (admin, auditor) or "custom" for user-defined roles + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + // creation timestamp for custom roles (only applicable for custom roles) + Created int64 `protobuf:"varint,4,opt,name=created,proto3" json:"created,omitempty"` + // user who created the role for custom roles (only applicable for custom roles) + CreatedBy string `protobuf:"bytes,5,opt,name=createdBy,proto3" json:"createdBy,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -126,7 +252,28 @@ func (x *OrgUnitRolesListEntry) GetDesc() string { return "" } -// org unit roles list response +func (x *OrgUnitRolesListEntry) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *OrgUnitRolesListEntry) GetCreated() int64 { + if x != nil { + return x.Created + } + return 0 +} + +func (x *OrgUnitRolesListEntry) GetCreatedBy() string { + if x != nil { + return x.CreatedBy + } + return "" +} + +// org unit roles list response - includes both built-in and custom roles type OrgUnitRolesListResp struct { state protoimpl.MessageState `protogen:"open.v1"` // list of roles available as part of the response @@ -172,21 +319,765 @@ func (x *OrgUnitRolesListResp) GetItems() []*OrgUnitRolesListEntry { return nil } +// Matching criteria types for resource-based permissions +type ResourceMatchCriteriaDefs struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceMatchCriteriaDefs) Reset() { + *x = ResourceMatchCriteriaDefs{} + mi := &file_org_unit_role_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceMatchCriteriaDefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceMatchCriteriaDefs) ProtoMessage() {} + +func (x *ResourceMatchCriteriaDefs) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceMatchCriteriaDefs.ProtoReflect.Descriptor instead. +func (*ResourceMatchCriteriaDefs) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{3} +} + +// Action types for role permissions +type RolePermissionActionDefs struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RolePermissionActionDefs) Reset() { + *x = RolePermissionActionDefs{} + mi := &file_org_unit_role_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RolePermissionActionDefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RolePermissionActionDefs) ProtoMessage() {} + +func (x *RolePermissionActionDefs) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RolePermissionActionDefs.ProtoReflect.Descriptor instead. +func (*RolePermissionActionDefs) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{4} +} + +// Resource matching criteria for fine-grained permission control +type ResourceMatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Matching criteria type + Criteria ResourceMatchCriteriaDefs_Criteria `protobuf:"varint,1,opt,name=criteria,proto3,enum=api.ResourceMatchCriteriaDefs_Criteria" json:"criteria,omitempty"` + // The matching key/pattern based on criteria + // For wildcard: supports * (e.g., "bucket-*", "*-prod", "*") + // For regex: valid regex pattern + // For exact/prefix/suffix: literal string + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceMatch) Reset() { + *x = ResourceMatch{} + mi := &file_org_unit_role_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceMatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceMatch) ProtoMessage() {} + +func (x *ResourceMatch) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceMatch.ProtoReflect.Descriptor instead. +func (*ResourceMatch) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{5} +} + +func (x *ResourceMatch) GetCriteria() ResourceMatchCriteriaDefs_Criteria { + if x != nil { + return x.Criteria + } + return ResourceMatchCriteriaDefs_Unspecified +} + +func (x *ResourceMatch) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +// Permission definition for custom roles +type RolePermission struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Resource name this permission applies to + Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + // Resource matching criteria (optional, defaults to wildcard with key="*") + Match *ResourceMatch `protobuf:"bytes,2,opt,name=match,proto3" json:"match,omitempty"` + // List of allowed verbs/actions for this resource (supports "*" for all verbs) + Verbs []string `protobuf:"bytes,3,rep,name=verbs,proto3" json:"verbs,omitempty"` + // Action type: Allow, Deny, or Log + Action RolePermissionActionDefs_Action `protobuf:"varint,4,opt,name=action,proto3,enum=api.RolePermissionActionDefs_Action" json:"action,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RolePermission) Reset() { + *x = RolePermission{} + mi := &file_org_unit_role_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RolePermission) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RolePermission) ProtoMessage() {} + +func (x *RolePermission) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RolePermission.ProtoReflect.Descriptor instead. +func (*RolePermission) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{6} +} + +func (x *RolePermission) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *RolePermission) GetMatch() *ResourceMatch { + if x != nil { + return x.Match + } + return nil +} + +func (x *RolePermission) GetVerbs() []string { + if x != nil { + return x.Verbs + } + return nil +} + +func (x *RolePermission) GetAction() RolePermissionActionDefs_Action { + if x != nil { + return x.Action + } + return RolePermissionActionDefs_Unspecified +} + +// Create custom role request +type CreateCustomRoleReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Organization unit ID + Ou string `protobuf:"bytes,1,opt,name=ou,proto3" json:"ou,omitempty"` + // Name of the custom role (must be unique within the org unit) + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Description explaining the purpose of this custom role + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // List of permissions granted by this custom role + Permissions []*RolePermission `protobuf:"bytes,4,rep,name=permissions,proto3" json:"permissions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateCustomRoleReq) Reset() { + *x = CreateCustomRoleReq{} + mi := &file_org_unit_role_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateCustomRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCustomRoleReq) ProtoMessage() {} + +func (x *CreateCustomRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateCustomRoleReq.ProtoReflect.Descriptor instead. +func (*CreateCustomRoleReq) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateCustomRoleReq) GetOu() string { + if x != nil { + return x.Ou + } + return "" +} + +func (x *CreateCustomRoleReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateCustomRoleReq) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CreateCustomRoleReq) GetPermissions() []*RolePermission { + if x != nil { + return x.Permissions + } + return nil +} + +// Create custom role response +type CreateCustomRoleResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateCustomRoleResp) Reset() { + *x = CreateCustomRoleResp{} + mi := &file_org_unit_role_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateCustomRoleResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCustomRoleResp) ProtoMessage() {} + +func (x *CreateCustomRoleResp) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateCustomRoleResp.ProtoReflect.Descriptor instead. +func (*CreateCustomRoleResp) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{8} +} + +// Update custom role request +type UpdateCustomRoleReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Organization unit ID + Ou string `protobuf:"bytes,1,opt,name=ou,proto3" json:"ou,omitempty"` + // Name of the custom role to update + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Updated description explaining the purpose of this custom role + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // Updated list of permissions granted by this custom role + Permissions []*RolePermission `protobuf:"bytes,4,rep,name=permissions,proto3" json:"permissions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateCustomRoleReq) Reset() { + *x = UpdateCustomRoleReq{} + mi := &file_org_unit_role_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateCustomRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCustomRoleReq) ProtoMessage() {} + +func (x *UpdateCustomRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateCustomRoleReq.ProtoReflect.Descriptor instead. +func (*UpdateCustomRoleReq) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateCustomRoleReq) GetOu() string { + if x != nil { + return x.Ou + } + return "" +} + +func (x *UpdateCustomRoleReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateCustomRoleReq) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *UpdateCustomRoleReq) GetPermissions() []*RolePermission { + if x != nil { + return x.Permissions + } + return nil +} + +// Update custom role response +type UpdateCustomRoleResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateCustomRoleResp) Reset() { + *x = UpdateCustomRoleResp{} + mi := &file_org_unit_role_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateCustomRoleResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCustomRoleResp) ProtoMessage() {} + +func (x *UpdateCustomRoleResp) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateCustomRoleResp.ProtoReflect.Descriptor instead. +func (*UpdateCustomRoleResp) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{10} +} + +// Get custom role request +type GetCustomRoleReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Organization unit ID + Ou string `protobuf:"bytes,1,opt,name=ou,proto3" json:"ou,omitempty"` + // Name of the custom role to retrieve + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCustomRoleReq) Reset() { + *x = GetCustomRoleReq{} + mi := &file_org_unit_role_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCustomRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCustomRoleReq) ProtoMessage() {} + +func (x *GetCustomRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCustomRoleReq.ProtoReflect.Descriptor instead. +func (*GetCustomRoleReq) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{11} +} + +func (x *GetCustomRoleReq) GetOu() string { + if x != nil { + return x.Ou + } + return "" +} + +func (x *GetCustomRoleReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Get custom role response +type GetCustomRoleResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Name of the custom role + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Description explaining the purpose of this custom role + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // List of permissions granted by this custom role + Permissions []*RolePermission `protobuf:"bytes,3,rep,name=permissions,proto3" json:"permissions,omitempty"` + // Timestamp when the role was created + Created int64 `protobuf:"varint,4,opt,name=created,proto3" json:"created,omitempty"` + // User who created this custom role + CreatedBy string `protobuf:"bytes,5,opt,name=createdBy,proto3" json:"createdBy,omitempty"` + // Timestamp when the role was last updated + Updated int64 `protobuf:"varint,6,opt,name=updated,proto3" json:"updated,omitempty"` + // User who last updated this custom role + UpdatedBy string `protobuf:"bytes,7,opt,name=updatedBy,proto3" json:"updatedBy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCustomRoleResp) Reset() { + *x = GetCustomRoleResp{} + mi := &file_org_unit_role_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCustomRoleResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCustomRoleResp) ProtoMessage() {} + +func (x *GetCustomRoleResp) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCustomRoleResp.ProtoReflect.Descriptor instead. +func (*GetCustomRoleResp) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{12} +} + +func (x *GetCustomRoleResp) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetCustomRoleResp) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *GetCustomRoleResp) GetPermissions() []*RolePermission { + if x != nil { + return x.Permissions + } + return nil +} + +func (x *GetCustomRoleResp) GetCreated() int64 { + if x != nil { + return x.Created + } + return 0 +} + +func (x *GetCustomRoleResp) GetCreatedBy() string { + if x != nil { + return x.CreatedBy + } + return "" +} + +func (x *GetCustomRoleResp) GetUpdated() int64 { + if x != nil { + return x.Updated + } + return 0 +} + +func (x *GetCustomRoleResp) GetUpdatedBy() string { + if x != nil { + return x.UpdatedBy + } + return "" +} + +// Delete custom role request +type DeleteCustomRoleReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Organization unit ID + Ou string `protobuf:"bytes,1,opt,name=ou,proto3" json:"ou,omitempty"` + // Name of the custom role to delete + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteCustomRoleReq) Reset() { + *x = DeleteCustomRoleReq{} + mi := &file_org_unit_role_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteCustomRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCustomRoleReq) ProtoMessage() {} + +func (x *DeleteCustomRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteCustomRoleReq.ProtoReflect.Descriptor instead. +func (*DeleteCustomRoleReq) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{13} +} + +func (x *DeleteCustomRoleReq) GetOu() string { + if x != nil { + return x.Ou + } + return "" +} + +func (x *DeleteCustomRoleReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Delete custom role response +type DeleteCustomRoleResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Confirmation message + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteCustomRoleResp) Reset() { + *x = DeleteCustomRoleResp{} + mi := &file_org_unit_role_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteCustomRoleResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCustomRoleResp) ProtoMessage() {} + +func (x *DeleteCustomRoleResp) ProtoReflect() protoreflect.Message { + mi := &file_org_unit_role_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteCustomRoleResp.ProtoReflect.Descriptor instead. +func (*DeleteCustomRoleResp) Descriptor() ([]byte, []int) { + return file_org_unit_role_proto_rawDescGZIP(), []int{14} +} + +func (x *DeleteCustomRoleResp) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + var File_org_unit_role_proto protoreflect.FileDescriptor const file_org_unit_role_proto_rawDesc = "" + "\n" + "\x13org-unit-role.proto\x12\x03api\x1a\x1cgoogle/api/annotations.proto\x1a\x17coreapis/api/role.proto\"%\n" + "\x13OrgUnitRolesListReq\x12\x0e\n" + - "\x02ou\x18\x01 \x01(\tR\x02ou\"?\n" + + "\x02ou\x18\x01 \x01(\tR\x02ou\"\x8b\x01\n" + "\x15OrgUnitRolesListEntry\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + - "\x04desc\x18\x02 \x01(\tR\x04desc\"H\n" + + "\x04desc\x18\x02 \x01(\tR\x04desc\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12\x18\n" + + "\acreated\x18\x04 \x01(\x03R\acreated\x12\x1c\n" + + "\tcreatedBy\x18\x05 \x01(\tR\tcreatedBy\"H\n" + "\x14OrgUnitRolesListResp\x120\n" + - "\x05items\x18\x02 \x03(\v2\x1a.api.OrgUnitRolesListEntryR\x05items2\x98\x01\n" + + "\x05items\x18\x02 \x03(\v2\x1a.api.OrgUnitRolesListEntryR\x05items\"o\n" + + "\x19ResourceMatchCriteriaDefs\"R\n" + + "\bCriteria\x12\x0f\n" + + "\vUnspecified\x10\x00\x12\a\n" + + "\x03Any\x10\x01\x12\t\n" + + "\x05Exact\x10\x02\x12\n" + + "\n" + + "\x06Prefix\x10\x03\x12\n" + + "\n" + + "\x06Suffix\x10\x04\x12\t\n" + + "\x05Regex\x10\x05\"S\n" + + "\x18RolePermissionActionDefs\"7\n" + + "\x06Action\x12\x0f\n" + + "\vUnspecified\x10\x00\x12\t\n" + + "\x05Allow\x10\x01\x12\b\n" + + "\x04Deny\x10\x02\x12\a\n" + + "\x03Log\x10\x03\"f\n" + + "\rResourceMatch\x12C\n" + + "\bcriteria\x18\x01 \x01(\x0e2'.api.ResourceMatchCriteriaDefs.CriteriaR\bcriteria\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\"\xaa\x01\n" + + "\x0eRolePermission\x12\x1a\n" + + "\bresource\x18\x01 \x01(\tR\bresource\x12(\n" + + "\x05match\x18\x02 \x01(\v2\x12.api.ResourceMatchR\x05match\x12\x14\n" + + "\x05verbs\x18\x03 \x03(\tR\x05verbs\x12<\n" + + "\x06action\x18\x04 \x01(\x0e2$.api.RolePermissionActionDefs.ActionR\x06action\"\x92\x01\n" + + "\x13CreateCustomRoleReq\x12\x0e\n" + + "\x02ou\x18\x01 \x01(\tR\x02ou\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x125\n" + + "\vpermissions\x18\x04 \x03(\v2\x13.api.RolePermissionR\vpermissions\"\x16\n" + + "\x14CreateCustomRoleResp\"\x92\x01\n" + + "\x13UpdateCustomRoleReq\x12\x0e\n" + + "\x02ou\x18\x01 \x01(\tR\x02ou\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x125\n" + + "\vpermissions\x18\x04 \x03(\v2\x13.api.RolePermissionR\vpermissions\"\x16\n" + + "\x14UpdateCustomRoleResp\"6\n" + + "\x10GetCustomRoleReq\x12\x0e\n" + + "\x02ou\x18\x01 \x01(\tR\x02ou\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"\xf0\x01\n" + + "\x11GetCustomRoleResp\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\x125\n" + + "\vpermissions\x18\x03 \x03(\v2\x13.api.RolePermissionR\vpermissions\x12\x18\n" + + "\acreated\x18\x04 \x01(\x03R\acreated\x12\x1c\n" + + "\tcreatedBy\x18\x05 \x01(\tR\tcreatedBy\x12\x18\n" + + "\aupdated\x18\x06 \x01(\x03R\aupdated\x12\x1c\n" + + "\tupdatedBy\x18\a \x01(\tR\tupdatedBy\"9\n" + + "\x13DeleteCustomRoleReq\x12\x0e\n" + + "\x02ou\x18\x01 \x01(\tR\x02ou\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"0\n" + + "\x14DeleteCustomRoleResp\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage2\xd7\x05\n" + "\vOrgUnitRole\x12\x88\x01\n" + "\x10ListOrgUnitRoles\x12\x18.api.OrgUnitRolesListReq\x1a\x19.api.OrgUnitRolesListResp\"?\x8a\xb5\x18\x19\n" + - "\rorg-unit-role\x12\x02ou\x1a\x04list\x82\xd3\xe4\x93\x02\x1c\x12\x1a/api/auth/v1/ou/{ou}/rolesB+Z)github.com/go-core-stack/auth-gateway/apib\x06proto3" + "\rorg-unit-role\x12\x02ou\x1a\x04list\x82\xd3\xe4\x93\x02\x1c\x12\x1a/api/auth/v1/ou/{ou}/roles\x12\x8c\x01\n" + + "\x10CreateCustomRole\x12\x18.api.CreateCustomRoleReq\x1a\x19.api.CreateCustomRoleResp\"C\x8a\xb5\x18\x1b\n" + + "\rorg-unit-role\x12\x02ou\x1a\x06create\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/api/auth/v1/ou/{ou}/role\x12\x93\x01\n" + + "\x10UpdateCustomRole\x12\x18.api.UpdateCustomRoleReq\x1a\x19.api.UpdateCustomRoleResp\"J\x8a\xb5\x18\x1b\n" + + "\rorg-unit-role\x12\x02ou\x1a\x06update\x82\xd3\xe4\x93\x02%:\x01*\x1a /api/auth/v1/ou/{ou}/role/{name}\x12\x84\x01\n" + + "\rGetCustomRole\x12\x15.api.GetCustomRoleReq\x1a\x16.api.GetCustomRoleResp\"D\x8a\xb5\x18\x18\n" + + "\rorg-unit-role\x12\x02ou\x1a\x03get\x82\xd3\xe4\x93\x02\"\x12 /api/auth/v1/ou/{ou}/role/{name}\x12\x90\x01\n" + + "\x10DeleteCustomRole\x12\x18.api.DeleteCustomRoleReq\x1a\x19.api.DeleteCustomRoleResp\"G\x8a\xb5\x18\x1b\n" + + "\rorg-unit-role\x12\x02ou\x1a\x06delete\x82\xd3\xe4\x93\x02\"* /api/auth/v1/ou/{ou}/role/{name}B+Z)github.com/go-core-stack/auth-gateway/apib\x06proto3" var ( file_org_unit_role_proto_rawDescOnce sync.Once @@ -200,21 +1091,50 @@ func file_org_unit_role_proto_rawDescGZIP() []byte { return file_org_unit_role_proto_rawDescData } -var file_org_unit_role_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_org_unit_role_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_org_unit_role_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_org_unit_role_proto_goTypes = []any{ - (*OrgUnitRolesListReq)(nil), // 0: api.OrgUnitRolesListReq - (*OrgUnitRolesListEntry)(nil), // 1: api.OrgUnitRolesListEntry - (*OrgUnitRolesListResp)(nil), // 2: api.OrgUnitRolesListResp + (ResourceMatchCriteriaDefs_Criteria)(0), // 0: api.ResourceMatchCriteriaDefs.Criteria + (RolePermissionActionDefs_Action)(0), // 1: api.RolePermissionActionDefs.Action + (*OrgUnitRolesListReq)(nil), // 2: api.OrgUnitRolesListReq + (*OrgUnitRolesListEntry)(nil), // 3: api.OrgUnitRolesListEntry + (*OrgUnitRolesListResp)(nil), // 4: api.OrgUnitRolesListResp + (*ResourceMatchCriteriaDefs)(nil), // 5: api.ResourceMatchCriteriaDefs + (*RolePermissionActionDefs)(nil), // 6: api.RolePermissionActionDefs + (*ResourceMatch)(nil), // 7: api.ResourceMatch + (*RolePermission)(nil), // 8: api.RolePermission + (*CreateCustomRoleReq)(nil), // 9: api.CreateCustomRoleReq + (*CreateCustomRoleResp)(nil), // 10: api.CreateCustomRoleResp + (*UpdateCustomRoleReq)(nil), // 11: api.UpdateCustomRoleReq + (*UpdateCustomRoleResp)(nil), // 12: api.UpdateCustomRoleResp + (*GetCustomRoleReq)(nil), // 13: api.GetCustomRoleReq + (*GetCustomRoleResp)(nil), // 14: api.GetCustomRoleResp + (*DeleteCustomRoleReq)(nil), // 15: api.DeleteCustomRoleReq + (*DeleteCustomRoleResp)(nil), // 16: api.DeleteCustomRoleResp } var file_org_unit_role_proto_depIdxs = []int32{ - 1, // 0: api.OrgUnitRolesListResp.items:type_name -> api.OrgUnitRolesListEntry - 0, // 1: api.OrgUnitRole.ListOrgUnitRoles:input_type -> api.OrgUnitRolesListReq - 2, // 2: api.OrgUnitRole.ListOrgUnitRoles:output_type -> api.OrgUnitRolesListResp - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 3, // 0: api.OrgUnitRolesListResp.items:type_name -> api.OrgUnitRolesListEntry + 0, // 1: api.ResourceMatch.criteria:type_name -> api.ResourceMatchCriteriaDefs.Criteria + 7, // 2: api.RolePermission.match:type_name -> api.ResourceMatch + 1, // 3: api.RolePermission.action:type_name -> api.RolePermissionActionDefs.Action + 8, // 4: api.CreateCustomRoleReq.permissions:type_name -> api.RolePermission + 8, // 5: api.UpdateCustomRoleReq.permissions:type_name -> api.RolePermission + 8, // 6: api.GetCustomRoleResp.permissions:type_name -> api.RolePermission + 2, // 7: api.OrgUnitRole.ListOrgUnitRoles:input_type -> api.OrgUnitRolesListReq + 9, // 8: api.OrgUnitRole.CreateCustomRole:input_type -> api.CreateCustomRoleReq + 11, // 9: api.OrgUnitRole.UpdateCustomRole:input_type -> api.UpdateCustomRoleReq + 13, // 10: api.OrgUnitRole.GetCustomRole:input_type -> api.GetCustomRoleReq + 15, // 11: api.OrgUnitRole.DeleteCustomRole:input_type -> api.DeleteCustomRoleReq + 4, // 12: api.OrgUnitRole.ListOrgUnitRoles:output_type -> api.OrgUnitRolesListResp + 10, // 13: api.OrgUnitRole.CreateCustomRole:output_type -> api.CreateCustomRoleResp + 12, // 14: api.OrgUnitRole.UpdateCustomRole:output_type -> api.UpdateCustomRoleResp + 14, // 15: api.OrgUnitRole.GetCustomRole:output_type -> api.GetCustomRoleResp + 16, // 16: api.OrgUnitRole.DeleteCustomRole:output_type -> api.DeleteCustomRoleResp + 12, // [12:17] is the sub-list for method output_type + 7, // [7:12] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_org_unit_role_proto_init() } @@ -227,13 +1147,14 @@ func file_org_unit_role_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_org_unit_role_proto_rawDesc), len(file_org_unit_role_proto_rawDesc)), - NumEnums: 0, - NumMessages: 3, + NumEnums: 2, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, GoTypes: file_org_unit_role_proto_goTypes, DependencyIndexes: file_org_unit_role_proto_depIdxs, + EnumInfos: file_org_unit_role_proto_enumTypes, MessageInfos: file_org_unit_role_proto_msgTypes, }.Build() File_org_unit_role_proto = out.File diff --git a/api/org-unit-role.pb.gw.go b/api/org-unit-role.pb.gw.go index 0017338..1fe7a4c 100644 --- a/api/org-unit-role.pb.gw.go +++ b/api/org-unit-role.pb.gw.go @@ -72,6 +72,212 @@ func local_request_OrgUnitRole_ListOrgUnitRoles_0(ctx context.Context, marshaler return msg, metadata, err } +func request_OrgUnitRole_CreateCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, client OrgUnitRoleClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + msg, err := client.CreateCustomRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OrgUnitRole_CreateCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, server OrgUnitRoleServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + msg, err := server.CreateCustomRole(ctx, &protoReq) + return msg, metadata, err +} + +func request_OrgUnitRole_UpdateCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, client OrgUnitRoleClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := client.UpdateCustomRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OrgUnitRole_UpdateCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, server OrgUnitRoleServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := server.UpdateCustomRole(ctx, &protoReq) + return msg, metadata, err +} + +func request_OrgUnitRole_GetCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, client OrgUnitRoleClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + io.Copy(io.Discard, req.Body) + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := client.GetCustomRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OrgUnitRole_GetCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, server OrgUnitRoleServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := server.GetCustomRole(ctx, &protoReq) + return msg, metadata, err +} + +func request_OrgUnitRole_DeleteCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, client OrgUnitRoleClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + io.Copy(io.Discard, req.Body) + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := client.DeleteCustomRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OrgUnitRole_DeleteCustomRole_0(ctx context.Context, marshaler runtime.Marshaler, server OrgUnitRoleServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteCustomRoleReq + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["ou"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "ou") + } + protoReq.Ou, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ou", err) + } + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := server.DeleteCustomRole(ctx, &protoReq) + return msg, metadata, err +} + // RegisterOrgUnitRoleHandlerServer registers the http handlers for service OrgUnitRole to "mux". // UnaryRPC :call OrgUnitRoleServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -98,6 +304,86 @@ func RegisterOrgUnitRoleHandlerServer(ctx context.Context, mux *runtime.ServeMux } forward_OrgUnitRole_ListOrgUnitRoles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_OrgUnitRole_CreateCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/api.OrgUnitRole/CreateCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OrgUnitRole_CreateCustomRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_CreateCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_OrgUnitRole_UpdateCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/api.OrgUnitRole/UpdateCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OrgUnitRole_UpdateCustomRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_UpdateCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_OrgUnitRole_GetCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/api.OrgUnitRole/GetCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OrgUnitRole_GetCustomRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_GetCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_OrgUnitRole_DeleteCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/api.OrgUnitRole/DeleteCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OrgUnitRole_DeleteCustomRole_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_DeleteCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -155,13 +441,89 @@ func RegisterOrgUnitRoleHandlerClient(ctx context.Context, mux *runtime.ServeMux } forward_OrgUnitRole_ListOrgUnitRoles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_OrgUnitRole_CreateCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/api.OrgUnitRole/CreateCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OrgUnitRole_CreateCustomRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_CreateCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_OrgUnitRole_UpdateCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/api.OrgUnitRole/UpdateCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OrgUnitRole_UpdateCustomRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_UpdateCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_OrgUnitRole_GetCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/api.OrgUnitRole/GetCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OrgUnitRole_GetCustomRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_GetCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_OrgUnitRole_DeleteCustomRole_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/api.OrgUnitRole/DeleteCustomRole", runtime.WithHTTPPathPattern("/api/auth/v1/ou/{ou}/role/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OrgUnitRole_DeleteCustomRole_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OrgUnitRole_DeleteCustomRole_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } var ( pattern_OrgUnitRole_ListOrgUnitRoles_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "auth", "v1", "ou", "roles"}, "")) + pattern_OrgUnitRole_CreateCustomRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "auth", "v1", "ou", "role"}, "")) + pattern_OrgUnitRole_UpdateCustomRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "auth", "v1", "ou", "role", "name"}, "")) + pattern_OrgUnitRole_GetCustomRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "auth", "v1", "ou", "role", "name"}, "")) + pattern_OrgUnitRole_DeleteCustomRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "auth", "v1", "ou", "role", "name"}, "")) ) var ( forward_OrgUnitRole_ListOrgUnitRoles_0 = runtime.ForwardResponseMessage + forward_OrgUnitRole_CreateCustomRole_0 = runtime.ForwardResponseMessage + forward_OrgUnitRole_UpdateCustomRole_0 = runtime.ForwardResponseMessage + forward_OrgUnitRole_GetCustomRole_0 = runtime.ForwardResponseMessage + forward_OrgUnitRole_DeleteCustomRole_0 = runtime.ForwardResponseMessage ) diff --git a/api/org-unit-role.pb.route.go b/api/org-unit-role.pb.route.go index 04bff78..0e01cbe 100644 --- a/api/org-unit-role.pb.route.go +++ b/api/org-unit-role.pb.route.go @@ -16,4 +16,32 @@ func init() { route.Scopes = append(route.Scopes, "ou") route.Verb = "list" RoutesOrgUnitRole = append(RoutesOrgUnitRole, route) + + // Adding Route information for CreateCustomRole RPC + route = model.NewRoute("/api/auth/v1/ou/{ou}/role", "POST") + route.Resource = "org-unit-role" + route.Scopes = append(route.Scopes, "ou") + route.Verb = "create" + RoutesOrgUnitRole = append(RoutesOrgUnitRole, route) + + // Adding Route information for UpdateCustomRole RPC + route = model.NewRoute("/api/auth/v1/ou/{ou}/role/{name}", "PUT") + route.Resource = "org-unit-role" + route.Scopes = append(route.Scopes, "ou") + route.Verb = "update" + RoutesOrgUnitRole = append(RoutesOrgUnitRole, route) + + // Adding Route information for GetCustomRole RPC + route = model.NewRoute("/api/auth/v1/ou/{ou}/role/{name}", "GET") + route.Resource = "org-unit-role" + route.Scopes = append(route.Scopes, "ou") + route.Verb = "get" + RoutesOrgUnitRole = append(RoutesOrgUnitRole, route) + + // Adding Route information for DeleteCustomRole RPC + route = model.NewRoute("/api/auth/v1/ou/{ou}/role/{name}", "DELETE") + route.Resource = "org-unit-role" + route.Scopes = append(route.Scopes, "ou") + route.Verb = "delete" + RoutesOrgUnitRole = append(RoutesOrgUnitRole, route) } diff --git a/api/org-unit-role.proto b/api/org-unit-role.proto index fcac3e0..c740995 100644 --- a/api/org-unit-role.proto +++ b/api/org-unit-role.proto @@ -12,7 +12,7 @@ option go_package = "github.com/go-core-stack/auth-gateway/api"; // Service provided to manage org unit roles service OrgUnitRole { - // Get List of available org unit roles, for specifc orgranisation + // Get List of available org unit roles (both built-in and custom), for specific organisation rpc ListOrgUnitRoles(OrgUnitRolesListReq) returns (OrgUnitRolesListResp) { option (google.api.http) = { get: "/api/auth/v1/ou/{ou}/roles" @@ -23,6 +23,56 @@ service OrgUnitRole { verb: "list" }; } + + // Create a new custom role for the organization unit + rpc CreateCustomRole(CreateCustomRoleReq) returns (CreateCustomRoleResp) { + option (google.api.http) = { + post: "/api/auth/v1/ou/{ou}/role" + body: "*" + }; + option (api.role) = { + resource: "org-unit-role" + scope: "ou" + verb: "create" + }; + } + + // Update an existing custom role for the organization unit + rpc UpdateCustomRole(UpdateCustomRoleReq) returns (UpdateCustomRoleResp) { + option (google.api.http) = { + put: "/api/auth/v1/ou/{ou}/role/{name}" + body: "*" + }; + option (api.role) = { + resource: "org-unit-role" + scope: "ou" + verb: "update" + }; + } + + // Get details of a specific custom role + rpc GetCustomRole(GetCustomRoleReq) returns (GetCustomRoleResp) { + option (google.api.http) = { + get: "/api/auth/v1/ou/{ou}/role/{name}" + }; + option (api.role) = { + resource: "org-unit-role" + scope: "ou" + verb: "get" + }; + } + + // Delete (soft delete) a custom role from the organization unit + rpc DeleteCustomRole(DeleteCustomRoleReq) returns (DeleteCustomRoleResp) { + option (google.api.http) = { + delete: "/api/auth/v1/ou/{ou}/role/{name}" + }; + option (api.role) = { + resource: "org-unit-role" + scope: "ou" + verb: "delete" + }; + } } // org unit roles list request @@ -37,9 +87,18 @@ message OrgUnitRolesListEntry { // role description, provding details about the role string desc = 2; + + // role type: "built-in" for system roles (admin, auditor) or "custom" for user-defined roles + string type = 3; + + // creation timestamp for custom roles (only applicable for custom roles) + int64 created = 4; + + // user who created the role for custom roles (only applicable for custom roles) + string createdBy = 5; } -// org unit roles list response +// org unit roles list response - includes both built-in and custom roles message OrgUnitRolesListResp { // eventually if we start working with longer list of roles // keep first index for count to provide pagination @@ -48,3 +107,148 @@ message OrgUnitRolesListResp { // list of roles available as part of the response repeated OrgUnitRolesListEntry items = 2; } + +// Matching criteria types for resource-based permissions +message ResourceMatchCriteriaDefs { + enum Criteria { + // Unspecified/default (treated as deny for security) + Unspecified = 0; + // Matches all resources (wildcard) + Any = 1; + // Exact resource name match + Exact = 2; + // Resource name starts with the pattern + Prefix = 3; + // Resource name ends with the pattern + Suffix = 4; + // Resource name matches the regex pattern + Regex = 5; + } +} + +// Action types for role permissions +message RolePermissionActionDefs { + enum Action { + // Unspecified action (invalid, will be denied) + Unspecified = 0; + // Allow the specified verbs on the resource + Allow = 1; + // Deny the specified verbs on the resource (takes precedence over Allow) + Deny = 2; + // Log the specified verbs on the resource (allows access but logs for audit) + Log = 3; + } +} + +// Resource matching criteria for fine-grained permission control +message ResourceMatch { + // Matching criteria type + ResourceMatchCriteriaDefs.Criteria criteria = 1; + + // The matching key/pattern based on criteria + // For wildcard: supports * (e.g., "bucket-*", "*-prod", "*") + // For regex: valid regex pattern + // For exact/prefix/suffix: literal string + string key = 2; +} + +// Permission definition for custom roles +message RolePermission { + // Resource name this permission applies to + string resource = 1; + + // Resource matching criteria (optional, defaults to wildcard with key="*") + ResourceMatch match = 2; + + // List of allowed verbs/actions for this resource (supports "*" for all verbs) + repeated string verbs = 3; + + // Action type: Allow, Deny, or Log + RolePermissionActionDefs.Action action = 4; +} + +// Create custom role request +message CreateCustomRoleReq { + // Organization unit ID + string ou = 1; + + // Name of the custom role (must be unique within the org unit) + string name = 2; + + // Description explaining the purpose of this custom role + string description = 3; + + // List of permissions granted by this custom role + repeated RolePermission permissions = 4; +} + +// Create custom role response +message CreateCustomRoleResp { +} + +// Update custom role request +message UpdateCustomRoleReq { + // Organization unit ID + string ou = 1; + + // Name of the custom role to update + string name = 2; + + // Updated description explaining the purpose of this custom role + string description = 3; + + // Updated list of permissions granted by this custom role + repeated RolePermission permissions = 4; +} + +// Update custom role response +message UpdateCustomRoleResp { +} + +// Get custom role request +message GetCustomRoleReq { + // Organization unit ID + string ou = 1; + + // Name of the custom role to retrieve + string name = 2; +} + +// Get custom role response +message GetCustomRoleResp { + // Name of the custom role + string name = 1; + + // Description explaining the purpose of this custom role + string description = 2; + + // List of permissions granted by this custom role + repeated RolePermission permissions = 3; + + // Timestamp when the role was created + int64 created = 4; + + // User who created this custom role + string createdBy = 5; + + // Timestamp when the role was last updated + int64 updated = 6; + + // User who last updated this custom role + string updatedBy = 7; +} + +// Delete custom role request +message DeleteCustomRoleReq { + // Organization unit ID + string ou = 1; + + // Name of the custom role to delete + string name = 2; +} + +// Delete custom role response +message DeleteCustomRoleResp { + // Confirmation message + string message = 1; +} \ No newline at end of file diff --git a/api/org-unit-role_grpc.pb.go b/api/org-unit-role_grpc.pb.go index d6d7d20..0782c89 100644 --- a/api/org-unit-role_grpc.pb.go +++ b/api/org-unit-role_grpc.pb.go @@ -23,6 +23,10 @@ const _ = grpc.SupportPackageIsVersion9 const ( OrgUnitRole_ListOrgUnitRoles_FullMethodName = "/api.OrgUnitRole/ListOrgUnitRoles" + OrgUnitRole_CreateCustomRole_FullMethodName = "/api.OrgUnitRole/CreateCustomRole" + OrgUnitRole_UpdateCustomRole_FullMethodName = "/api.OrgUnitRole/UpdateCustomRole" + OrgUnitRole_GetCustomRole_FullMethodName = "/api.OrgUnitRole/GetCustomRole" + OrgUnitRole_DeleteCustomRole_FullMethodName = "/api.OrgUnitRole/DeleteCustomRole" ) // OrgUnitRoleClient is the client API for OrgUnitRole service. @@ -31,8 +35,16 @@ const ( // // Service provided to manage org unit roles type OrgUnitRoleClient interface { - // Get List of available org unit roles, for specifc orgranisation + // Get List of available org unit roles (both built-in and custom), for specific organisation ListOrgUnitRoles(ctx context.Context, in *OrgUnitRolesListReq, opts ...grpc.CallOption) (*OrgUnitRolesListResp, error) + // Create a new custom role for the organization unit + CreateCustomRole(ctx context.Context, in *CreateCustomRoleReq, opts ...grpc.CallOption) (*CreateCustomRoleResp, error) + // Update an existing custom role for the organization unit + UpdateCustomRole(ctx context.Context, in *UpdateCustomRoleReq, opts ...grpc.CallOption) (*UpdateCustomRoleResp, error) + // Get details of a specific custom role + GetCustomRole(ctx context.Context, in *GetCustomRoleReq, opts ...grpc.CallOption) (*GetCustomRoleResp, error) + // Delete (soft delete) a custom role from the organization unit + DeleteCustomRole(ctx context.Context, in *DeleteCustomRoleReq, opts ...grpc.CallOption) (*DeleteCustomRoleResp, error) } type orgUnitRoleClient struct { @@ -53,14 +65,62 @@ func (c *orgUnitRoleClient) ListOrgUnitRoles(ctx context.Context, in *OrgUnitRol return out, nil } +func (c *orgUnitRoleClient) CreateCustomRole(ctx context.Context, in *CreateCustomRoleReq, opts ...grpc.CallOption) (*CreateCustomRoleResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateCustomRoleResp) + err := c.cc.Invoke(ctx, OrgUnitRole_CreateCustomRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *orgUnitRoleClient) UpdateCustomRole(ctx context.Context, in *UpdateCustomRoleReq, opts ...grpc.CallOption) (*UpdateCustomRoleResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateCustomRoleResp) + err := c.cc.Invoke(ctx, OrgUnitRole_UpdateCustomRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *orgUnitRoleClient) GetCustomRole(ctx context.Context, in *GetCustomRoleReq, opts ...grpc.CallOption) (*GetCustomRoleResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCustomRoleResp) + err := c.cc.Invoke(ctx, OrgUnitRole_GetCustomRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *orgUnitRoleClient) DeleteCustomRole(ctx context.Context, in *DeleteCustomRoleReq, opts ...grpc.CallOption) (*DeleteCustomRoleResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteCustomRoleResp) + err := c.cc.Invoke(ctx, OrgUnitRole_DeleteCustomRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // OrgUnitRoleServer is the server API for OrgUnitRole service. // All implementations must embed UnimplementedOrgUnitRoleServer // for forward compatibility. // // Service provided to manage org unit roles type OrgUnitRoleServer interface { - // Get List of available org unit roles, for specifc orgranisation + // Get List of available org unit roles (both built-in and custom), for specific organisation ListOrgUnitRoles(context.Context, *OrgUnitRolesListReq) (*OrgUnitRolesListResp, error) + // Create a new custom role for the organization unit + CreateCustomRole(context.Context, *CreateCustomRoleReq) (*CreateCustomRoleResp, error) + // Update an existing custom role for the organization unit + UpdateCustomRole(context.Context, *UpdateCustomRoleReq) (*UpdateCustomRoleResp, error) + // Get details of a specific custom role + GetCustomRole(context.Context, *GetCustomRoleReq) (*GetCustomRoleResp, error) + // Delete (soft delete) a custom role from the organization unit + DeleteCustomRole(context.Context, *DeleteCustomRoleReq) (*DeleteCustomRoleResp, error) mustEmbedUnimplementedOrgUnitRoleServer() } @@ -74,6 +134,18 @@ type UnimplementedOrgUnitRoleServer struct{} func (UnimplementedOrgUnitRoleServer) ListOrgUnitRoles(context.Context, *OrgUnitRolesListReq) (*OrgUnitRolesListResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListOrgUnitRoles not implemented") } +func (UnimplementedOrgUnitRoleServer) CreateCustomRole(context.Context, *CreateCustomRoleReq) (*CreateCustomRoleResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateCustomRole not implemented") +} +func (UnimplementedOrgUnitRoleServer) UpdateCustomRole(context.Context, *UpdateCustomRoleReq) (*UpdateCustomRoleResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateCustomRole not implemented") +} +func (UnimplementedOrgUnitRoleServer) GetCustomRole(context.Context, *GetCustomRoleReq) (*GetCustomRoleResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCustomRole not implemented") +} +func (UnimplementedOrgUnitRoleServer) DeleteCustomRole(context.Context, *DeleteCustomRoleReq) (*DeleteCustomRoleResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteCustomRole not implemented") +} func (UnimplementedOrgUnitRoleServer) mustEmbedUnimplementedOrgUnitRoleServer() {} func (UnimplementedOrgUnitRoleServer) testEmbeddedByValue() {} @@ -113,6 +185,78 @@ func _OrgUnitRole_ListOrgUnitRoles_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _OrgUnitRole_CreateCustomRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateCustomRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrgUnitRoleServer).CreateCustomRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrgUnitRole_CreateCustomRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrgUnitRoleServer).CreateCustomRole(ctx, req.(*CreateCustomRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrgUnitRole_UpdateCustomRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateCustomRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrgUnitRoleServer).UpdateCustomRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrgUnitRole_UpdateCustomRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrgUnitRoleServer).UpdateCustomRole(ctx, req.(*UpdateCustomRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrgUnitRole_GetCustomRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCustomRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrgUnitRoleServer).GetCustomRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrgUnitRole_GetCustomRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrgUnitRoleServer).GetCustomRole(ctx, req.(*GetCustomRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrgUnitRole_DeleteCustomRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteCustomRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrgUnitRoleServer).DeleteCustomRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrgUnitRole_DeleteCustomRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrgUnitRoleServer).DeleteCustomRole(ctx, req.(*DeleteCustomRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + // OrgUnitRole_ServiceDesc is the grpc.ServiceDesc for OrgUnitRole service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -124,6 +268,22 @@ var OrgUnitRole_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListOrgUnitRoles", Handler: _OrgUnitRole_ListOrgUnitRoles_Handler, }, + { + MethodName: "CreateCustomRole", + Handler: _OrgUnitRole_CreateCustomRole_Handler, + }, + { + MethodName: "UpdateCustomRole", + Handler: _OrgUnitRole_UpdateCustomRole_Handler, + }, + { + MethodName: "GetCustomRole", + Handler: _OrgUnitRole_GetCustomRole_Handler, + }, + { + MethodName: "DeleteCustomRole", + Handler: _OrgUnitRole_DeleteCustomRole_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "org-unit-role.proto", diff --git a/api/swagger/apidocs.swagger.json b/api/swagger/apidocs.swagger.json index d2af993..162bf95 100644 --- a/api/swagger/apidocs.swagger.json +++ b/api/swagger/apidocs.swagger.json @@ -220,9 +220,170 @@ ] } }, + "/api/auth/v1/ou/{ou}/role": { + "post": { + "summary": "Create a new custom role for the organization unit", + "operationId": "OrgUnitRole_CreateCustomRole", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiCreateCustomRoleResp" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "ou", + "description": "Organization unit ID", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/OrgUnitRoleCreateCustomRoleBody" + } + } + ], + "tags": [ + "OrgUnitRole" + ] + } + }, + "/api/auth/v1/ou/{ou}/role/{name}": { + "get": { + "summary": "Get details of a specific custom role", + "operationId": "OrgUnitRole_GetCustomRole", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiGetCustomRoleResp" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "ou", + "description": "Organization unit ID", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "name", + "description": "Name of the custom role to retrieve", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "OrgUnitRole" + ] + }, + "delete": { + "summary": "Delete (soft delete) a custom role from the organization unit", + "operationId": "OrgUnitRole_DeleteCustomRole", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiDeleteCustomRoleResp" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "ou", + "description": "Organization unit ID", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "name", + "description": "Name of the custom role to delete", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "OrgUnitRole" + ] + }, + "put": { + "summary": "Update an existing custom role for the organization unit", + "operationId": "OrgUnitRole_UpdateCustomRole", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiUpdateCustomRoleResp" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "ou", + "description": "Organization unit ID", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "name", + "description": "Name of the custom role to update", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/OrgUnitRoleUpdateCustomRoleBody" + } + } + ], + "tags": [ + "OrgUnitRole" + ] + } + }, "/api/auth/v1/ou/{ou}/roles": { "get": { - "summary": "Get List of available org unit roles, for specifc orgranisation", + "summary": "Get List of available org unit roles (both built-in and custom), for specific organisation", "operationId": "OrgUnitRole_ListOrgUnitRoles", "responses": { "200": { @@ -2343,6 +2504,46 @@ }, "title": "Identity provider update request" }, + "OrgUnitRoleCreateCustomRoleBody": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name of the custom role (must be unique within the org unit)" + }, + "description": { + "type": "string", + "title": "Description explaining the purpose of this custom role" + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiRolePermission" + }, + "title": "List of permissions granted by this custom role" + } + }, + "title": "Create custom role request" + }, + "OrgUnitRoleUpdateCustomRoleBody": { + "type": "object", + "properties": { + "description": { + "type": "string", + "title": "Updated description explaining the purpose of this custom role" + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiRolePermission" + }, + "title": "Updated list of permissions granted by this custom role" + } + }, + "title": "Update custom role request" + }, "OrgUnitUpdateOrgUnitBody": { "type": "object", "properties": { @@ -2381,6 +2582,30 @@ }, "title": "org unit user role update request" }, + "ResourceMatchCriteriaDefsCriteria": { + "type": "string", + "enum": [ + "Unspecified", + "Any", + "Exact", + "Prefix", + "Suffix", + "Regex" + ], + "default": "Unspecified", + "title": "- Unspecified: Unspecified/default (treated as deny for security)\n - Any: Matches all resources (wildcard)\n - Exact: Exact resource name match\n - Prefix: Resource name starts with the pattern\n - Suffix: Resource name ends with the pattern\n - Regex: Resource name matches the regex pattern" + }, + "RolePermissionActionDefsAction": { + "type": "string", + "enum": [ + "Unspecified", + "Allow", + "Deny", + "Log" + ], + "default": "Unspecified", + "title": "- Unspecified: Unspecified action (invalid, will be denied)\n - Allow: Allow the specified verbs on the resource\n - Deny: Deny the specified verbs on the resource (takes precedence over Allow)\n - Log: Log the specified verbs on the resource (allows access but logs for audit)" + }, "apiApiKeyCreateReq": { "type": "object", "properties": { @@ -2494,6 +2719,10 @@ } } }, + "apiCreateCustomRoleResp": { + "type": "object", + "title": "Create custom role response" + }, "apiCustomerAddReq": { "type": "object", "properties": { @@ -2601,6 +2830,56 @@ "apiDefaultOrgUnitResp": { "type": "object" }, + "apiDeleteCustomRoleResp": { + "type": "object", + "properties": { + "message": { + "type": "string", + "title": "Confirmation message" + } + }, + "title": "Delete custom role response" + }, + "apiGetCustomRoleResp": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name of the custom role" + }, + "description": { + "type": "string", + "title": "Description explaining the purpose of this custom role" + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiRolePermission" + }, + "title": "List of permissions granted by this custom role" + }, + "created": { + "type": "string", + "format": "int64", + "title": "Timestamp when the role was created" + }, + "createdBy": { + "type": "string", + "title": "User who created this custom role" + }, + "updated": { + "type": "string", + "format": "int64", + "title": "Timestamp when the role was last updated" + }, + "updatedBy": { + "type": "string", + "title": "User who last updated this custom role" + } + }, + "title": "Get custom role response" + }, "apiGoogleIDPConfig": { "type": "object", "properties": { @@ -3144,6 +3423,19 @@ "desc": { "type": "string", "title": "role description, provding details about the role" + }, + "type": { + "type": "string", + "title": "role type: \"built-in\" for system roles (admin, auditor) or \"custom\" for user-defined roles" + }, + "created": { + "type": "string", + "format": "int64", + "title": "creation timestamp for custom roles (only applicable for custom roles)" + }, + "createdBy": { + "type": "string", + "title": "user who created the role for custom roles (only applicable for custom roles)" } } }, @@ -3160,7 +3452,7 @@ } }, "description": "eventually if we start working with longer list of roles\n keep first index for count to provide pagination\n int32 count = 1;", - "title": "org unit roles list response" + "title": "org unit roles list response - includes both built-in and custom roles" }, "apiOrgUnitUpdateResp": { "type": "object" @@ -3314,6 +3606,45 @@ }, "title": "resource get response" }, + "apiResourceMatch": { + "type": "object", + "properties": { + "criteria": { + "$ref": "#/definitions/ResourceMatchCriteriaDefsCriteria", + "title": "Matching criteria type" + }, + "key": { + "type": "string", + "title": "The matching key/pattern based on criteria\nFor wildcard: supports * (e.g., \"bucket-*\", \"*-prod\", \"*\")\nFor regex: valid regex pattern\nFor exact/prefix/suffix: literal string" + } + }, + "title": "Resource matching criteria for fine-grained permission control" + }, + "apiRolePermission": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "title": "Resource name this permission applies to" + }, + "match": { + "$ref": "#/definitions/apiResourceMatch", + "title": "Resource matching criteria (optional, defaults to wildcard with key=\"*\")" + }, + "verbs": { + "type": "array", + "items": { + "type": "string" + }, + "title": "List of allowed verbs/actions for this resource (supports \"*\" for all verbs)" + }, + "action": { + "$ref": "#/definitions/RolePermissionActionDefsAction", + "title": "Action type: Allow, Deny, or Log" + } + }, + "title": "Permission definition for custom roles" + }, "apiTenantAdminCreateConfig": { "type": "object", "properties": { @@ -3751,6 +4082,10 @@ }, "title": "tenant list response" }, + "apiUpdateCustomRoleResp": { + "type": "object", + "title": "Update custom role response" + }, "apiUserCreateReq": { "type": "object", "properties": { diff --git a/main.go b/main.go index 1f17378..91c8051 100644 --- a/main.go +++ b/main.go @@ -434,6 +434,17 @@ func main() { log.Panicf("failed to start event logger for Org Unit User table: %s", err) } + // locate Org Unit Custom Role table + ouCustomRoleTbl, err := table.LocateOrgUnitCustomRoleTable(client) + if err != nil { + log.Panicf("failed to locate Org Unit Custom Role table: %s", err) + } + + err = ouCustomRoleTbl.StartEventLogger() + if err != nil { + log.Panicf("failed to start event logger for Org Unit Custom Role table: %s", err) + } + // ensure that the root tenant exists to work with as the default // tenancy locateRootTenant() diff --git a/pkg/gateway/routes.go b/pkg/gateway/routes.go index 35363e3..2e28f25 100644 --- a/pkg/gateway/routes.go +++ b/pkg/gateway/routes.go @@ -23,6 +23,8 @@ type routeData struct { isRoot bool isUserSpecific bool scopes []string + resource string + verb string } type routeNodes map[route.MethodType]routeData @@ -49,6 +51,8 @@ func populateRoutes(routes *route.RouteTable) { isRoot: utils.Dereference(r.IsRoot), isUserSpecific: utils.Dereference(r.IsUserSpecific), scopes: r.Scopes, + resource: r.Resource, + verb: r.Verb, }, } nRoutes.Insert(r.Key.Url, node) @@ -60,6 +64,8 @@ func populateRoutes(routes *route.RouteTable) { isRoot: utils.Dereference(r.IsRoot), isUserSpecific: utils.Dereference(r.IsUserSpecific), scopes: r.Scopes, + resource: r.Resource, + verb: r.Verb, } } } @@ -69,7 +75,7 @@ func populateRoutes(routes *route.RouteTable) { gwRoutes = nRoutes } -func matchRoute(m string, url string) (*routeData, string, error) { +func matchRoute(m string, url string) (*routeData, string, []string, []string, error) { var node *routeNodes var ok bool var keys, values []string @@ -82,7 +88,7 @@ func matchRoute(m string, url string) (*routeData, string, error) { }() if !ok { - return nil, "", errors.Wrapf(errors.NotFound, "route not found for %s", url) + return nil, "", nil, nil, errors.Wrapf(errors.NotFound, "route not found for %s", url) } var method route.MethodType @@ -106,19 +112,19 @@ func matchRoute(m string, url string) (*routeData, string, error) { case http.MethodTrace: method = route.TRACE default: - return nil, "", errors.Wrapf(errors.InvalidArgument, "invalid method %s", m) + return nil, "", nil, nil, errors.Wrapf(errors.InvalidArgument, "invalid method %s", m) } data, ok := (*node)[method] if !ok { - return nil, "", errors.Wrapf(errors.NotFound, "route not found for %s", url) + return nil, "", nil, nil, errors.Wrapf(errors.NotFound, "route not found for %s", url) } orgUnit := "" switch len(data.scopes) { case 1: if data.scopes[0] != "ou" { - return nil, "", errors.Wrapf(errors.InvalidArgument, "invalid scope %s for %s", data.scopes[0], url) + return nil, "", nil, nil, errors.Wrapf(errors.InvalidArgument, "invalid scope %s for %s", data.scopes[0], url) } for i, k := range keys { if k == "ou" { @@ -127,13 +133,13 @@ func matchRoute(m string, url string) (*routeData, string, error) { } } if orgUnit == "" { - return nil, "", errors.Wrapf(errors.InvalidArgument, "org unit not found") + return nil, "", nil, nil, errors.Wrapf(errors.InvalidArgument, "org unit not found") } case 0: break default: - return nil, "", errors.Wrapf(errors.InvalidArgument, "multiple scopes found for %s", url) + return nil, "", nil, nil, errors.Wrapf(errors.InvalidArgument, "multiple scopes found for %s", url) } - return &data, orgUnit, nil + return &data, orgUnit, keys, values, nil } diff --git a/pkg/gateway/server.go b/pkg/gateway/server.go index 8012794..603319d 100644 --- a/pkg/gateway/server.go +++ b/pkg/gateway/server.go @@ -12,6 +12,7 @@ import ( "net/http" "net/http/httputil" "os" + "regexp" "slices" "strings" "sync" @@ -44,14 +45,15 @@ var logger *zap.Logger type gateway struct { http.Handler - validator hash.Validator - apiKeys *table.ApiKeyTable - userTbl *table.UserTable - routes *route.RouteTable - ouTbl *table.OrgUnitTable - ouUserTbl *table.OrgUnitUserTable - proxyV1 *httputil.ReverseProxy - proxyV2 *httputil.ReverseProxy + validator hash.Validator + apiKeys *table.ApiKeyTable + userTbl *table.UserTable + routes *route.RouteTable + ouTbl *table.OrgUnitTable + ouUserTbl *table.OrgUnitUserTable + ouCustomRoleTbl *table.OrgUnitCustomRoleTable + proxyV1 *httputil.ReverseProxy + proxyV2 *httputil.ReverseProxy } type gatewayReconciler struct { @@ -206,7 +208,8 @@ func (s *gateway) AuthenticateRequest(r *http.Request) (*common.AuthInfo, error) // performOrgUnitRoleCheck checks if the Org unit role associated with the user // allows the requested access, returns true if the role allows access -func (s *gateway) performOrgUnitRoleCheck(authInfo *common.AuthInfo, ou string, r *http.Request) bool { +// For non-list operations, resourceInstance is the name/id of the specific resource being accessed +func (s *gateway) performOrgUnitRoleCheck(authInfo *common.AuthInfo, ou string, resource, verb, resourceInstance string, r *http.Request) bool { ouUserKey := &table.OrgUnitUserKey{ Tenant: authInfo.Realm, Username: authInfo.UserName, @@ -219,6 +222,8 @@ func (s *gateway) performOrgUnitRoleCheck(authInfo *common.AuthInfo, ou string, } return false } + + // Handle built-in roles switch ouUser.Role { case "admin": // wildcard access to the org unit @@ -230,7 +235,139 @@ func (s *gateway) performOrgUnitRoleCheck(authInfo *common.AuthInfo, ou string, } return false } - return false + + // Check custom role permissions + customRole, err := s.ouCustomRoleTbl.FindByNameAndOrgUnit(r.Context(), authInfo.Realm, ou, ouUser.Role) + if err != nil { + if !errors.IsNotFound(err) { + log.Printf("failed to find custom role %s for org unit %s: %s", ouUser.Role, ou, err) + } + return false + } + + // Evaluate permissions + allowed, shouldLog := s.evaluateCustomRolePermissions(customRole.Permissions, resource, verb, resourceInstance) + + // Log entry if Log action was matched + if shouldLog { + log.Printf("[LOG] User: %s, Tenant: %s, OrgUnit: %s, Role: %s, Resource: %s, Verb: %s, Instance: %s, Method: %s, Path: %s, Allowed: %v", + authInfo.UserName, authInfo.Realm, ou, ouUser.Role, resource, verb, resourceInstance, r.Method, r.URL.Path, allowed) + } + + return allowed +} + +// evaluateCustomRolePermissions checks if the custom role's permissions allow the requested resource and verb +// For non-list operations, resourceInstance is checked against the permission's match criteria +// Returns (allowed bool, shouldLog bool) +func (s *gateway) evaluateCustomRolePermissions(permissions []*table.RolePermission, resource, verb, resourceInstance string) (bool, bool) { + var allowMatched bool + var denyMatched bool + var logMatched bool + + for _, perm := range permissions { + // Check if resource matches + if !s.matchesResource(perm.Resource, perm.Match, resource) { + continue + } + + // Check if verb matches + verbMatches := false + for _, allowedVerb := range perm.Verbs { + if allowedVerb == "*" || allowedVerb == verb { + verbMatches = true + break + } + } + + if !verbMatches { + continue + } + + // For non-list operations, check if the resource instance matches the criteria + // List operations skip instance matching (show all, filter on individual access) + if verb != "list" && resourceInstance != "" { + if !s.matchesResourceInstance(resourceInstance, perm.Match) { + continue + } + } + + // Apply action (Deny takes precedence, Log enables audit logging) + switch perm.Action { + case table.RolePermissionActionDeny: + denyMatched = true + case table.RolePermissionActionLog: + // Log action allows access but marks it for audit logging + logMatched = true + allowMatched = true + case table.RolePermissionActionAllow, table.RolePermissionActionUnspecified: + // Treat UNSPECIFIED or empty as Allow (default permissive behavior) + allowMatched = true + } + } + + // Deny takes precedence over Allow and Log + if denyMatched { + return false, false + } + + return allowMatched, logMatched +} + +// matchesResource checks if a requested resource matches the permission's resource pattern +// This function performs resource type matching (e.g., "s3-object", "bucket") +func (s *gateway) matchesResource(permResource string, match *table.ResourceMatch, requestedResource string) bool { + // Handle wildcard resource matching (e.g., "*" matches all resources) + if permResource == "*" { + return true + } + + // Simple equality check - does the permission resource match the requested resource? + return permResource == requestedResource +} + +// matchesResourceInstance checks if a resource instance name matches the permission's match criteria +// Returns true if the instance matches the criteria, or if no criteria is specified +func (s *gateway) matchesResourceInstance(instanceName string, match *table.ResourceMatch) bool { + // If no match criteria specified, allow all instances of this resource type + if match == nil || match.Key == "" { + return true + } + + switch match.Criteria { + case table.ResourceMatchCriteriaExact: + return instanceName == match.Key + + case table.ResourceMatchCriteriaPrefix: + return strings.HasPrefix(instanceName, match.Key) + + case table.ResourceMatchCriteriaSuffix: + return strings.HasSuffix(instanceName, match.Key) + + case table.ResourceMatchCriteriaRegex: + matched, err := regexp.MatchString(match.Key, instanceName) + if err != nil { + log.Printf("Invalid regex pattern %s: %s", match.Key, err) + return false + } + return matched + + case table.ResourceMatchCriteriaWildcard, "": + // Default to wildcard matching + // Convert wildcard pattern to regex + pattern := strings.ReplaceAll(match.Key, "*", ".*") + pattern = "^" + pattern + "$" + matched, err := regexp.MatchString(pattern, instanceName) + if err != nil { + log.Printf("Invalid wildcard pattern %s: %s", match.Key, err) + return false + } + return matched + + default: + log.Printf("Unknown match criteria: %s", match.Criteria) + return false + } } func (s *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -242,13 +379,24 @@ func (s *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { // RawPath will be an empty string path = r.URL.Path } - match, orgUnit, err := matchRoute(r.Method, path) + match, orgUnit, keys, values, err := matchRoute(r.Method, path) if err != nil { status = http.StatusNotFound http.Error(w, fmt.Sprintf("No route found for %s %s", r.Method, path), status) return } + // Extract resource instance name from URL path parameters + // Common patterns: /bucket/{name}, /s3-object/{name}, /user/{username} + resourceInstance := "" + for i, k := range keys { + // Look for common resource identifier keys + if k == "name" || k == "id" || k == "username" || k == "bucket" || k == "object" { + resourceInstance = values[i] + break + } + } + var authInfo *common.AuthInfo defer func() { if status != 0 { @@ -296,7 +444,7 @@ func (s *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { if orgUnit != "" { // check if Org Unit Role associated with user, allows the // requested access - allow = s.performOrgUnitRoleCheck(authInfo, orgUnit, r) + allow = s.performOrgUnitRoleCheck(authInfo, orgUnit, match.resource, match.verb, resourceInstance, r) } if !allow { status = http.StatusForbidden @@ -466,6 +614,11 @@ func New() http.Handler { log.Panicf("unable to get org unit user table: %s", err) } + ouCustomRoleTbl, err := table.GetOrgUnitCustomRoleTable() + if err != nil { + log.Panicf("unable to get org unit custom role table: %s", err) + } + director := func(req *http.Request) { // we don't use director we will handle request modification // of our own @@ -481,12 +634,13 @@ func New() http.Handler { } gateway := &gateway{ - validator: hash.NewValidator(300), // Allow an API request to be valid for 5 mins, to handle offer if any - apiKeys: apiKeys, - userTbl: userTbl, - routes: routes, - ouTbl: ouTbl, - ouUserTbl: ouUserTbl, + validator: hash.NewValidator(300), // Allow an API request to be valid for 5 mins, to handle offer if any + apiKeys: apiKeys, + userTbl: userTbl, + routes: routes, + ouTbl: ouTbl, + ouUserTbl: ouUserTbl, + ouCustomRoleTbl: ouCustomRoleTbl, proxyV1: &httputil.ReverseProxy{ Director: director, Transport: tr1, diff --git a/pkg/server/org-unit-role.go b/pkg/server/org-unit-role.go index a862b31..c0371a0 100644 --- a/pkg/server/org-unit-role.go +++ b/pkg/server/org-unit-role.go @@ -5,43 +5,473 @@ package server import ( "context" + "fmt" "log" + "time" + auth "github.com/go-core-stack/auth/context" "github.com/go-core-stack/auth/route" + "github.com/go-core-stack/core/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/go-core-stack/auth-gateway/api" "github.com/go-core-stack/auth-gateway/pkg/model" + "github.com/go-core-stack/auth-gateway/pkg/table" ) type OrgUnitRoleServer struct { api.UnimplementedOrgUnitRoleServer + customRoleTable *table.OrgUnitCustomRoleTable } +// ListOrgUnitRoles returns both built-in and custom roles for an organization unit func (s *OrgUnitRoleServer) ListOrgUnitRoles(ctx context.Context, req *api.OrgUnitRolesListReq) (*api.OrgUnitRolesListResp, error) { log.Printf("received list request for org unit roles: %v", req) - resp := &api.OrgUnitRolesListResp{ - Items: []*api.OrgUnitRolesListEntry{ - { - Name: "default", - Desc: "Standard user role to provide access to all the resources available in the Organization Unit", - }, - { - Name: "admin", - Desc: "Administrator role to provide access to everything in the Organization Unit including management of users and resources", - }, - { - Name: "auditor", - Desc: "Auditor role to provider read-only access to all the resources available in the Organization Unit", - }, + + authInfo, _ := auth.GetAuthInfoFromContext(ctx) + if authInfo == nil { + return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") + } + + items := []*api.OrgUnitRolesListEntry{ + { + Name: "admin", + Desc: "Administrator role to provide access to everything in the Organization Unit including management of users and resources", + Type: "built-in", + Created: 0, + CreatedBy: "", + }, + { + Name: "auditor", + Desc: "Auditor role to provide read-only access to all the resources available in the Organization Unit", + Type: "built-in", + Created: 0, + CreatedBy: "", }, } + + // Fetch custom roles from database + customRoles, err := s.customRoleTable.GetByOrgUnit(ctx, authInfo.Realm, req.Ou, 0, 1000) + if err != nil { + log.Printf("failed to fetch custom roles: %s", err) + } else { + for _, role := range customRoles { + items = append(items, &api.OrgUnitRolesListEntry{ + Name: role.Key.Name, + Desc: role.Description, + Type: "custom", + Created: role.Created, + CreatedBy: role.CreatedBy, + }) + } + } + + resp := &api.OrgUnitRolesListResp{ + Items: items, + } return resp, nil } +// CreateCustomRole creates a new custom role for the org unit +func (s *OrgUnitRoleServer) CreateCustomRole(ctx context.Context, req *api.CreateCustomRoleReq) (*api.CreateCustomRoleResp, error) { + authInfo, _ := auth.GetAuthInfoFromContext(ctx) + if authInfo == nil { + return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") + } + + if err := s.validateCreateCustomRoleRequest(req); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "Invalid request: %s", err) + } + + // Validate that role name is not one of the system reserved names + if req.Name == "admin" || req.Name == "default" || req.Name == "auditor" { + return nil, status.Errorf(codes.InvalidArgument, "Role name '%s' is reserved and cannot be used for custom roles", req.Name) + } + + // Check if there's an existing role (including soft-deleted ones) with the same name + existingRole, err := s.customRoleTable.FindAnyByNameAndOrgUnit(ctx, authInfo.Realm, req.Ou, req.Name) + if err == nil { + // Role exists - check if it's active or soft-deleted with bindings + if existingRole.Active == nil || *existingRole.Active { + return nil, status.Errorf(codes.AlreadyExists, "Custom role '%s' already exists in organization unit", req.Name) + } + + // Soft-deleted role exists - check if it has bindings + hasBindings, err := s.customRoleTable.HasBindings(ctx, authInfo.Realm, req.Ou, req.Name) + if err != nil { + log.Printf("failed to check bindings for role %s: %s", req.Name, err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + + if hasBindings { + return nil, status.Errorf(codes.AlreadyExists, "Custom role '%s' already exists in organization unit", req.Name) + } + + // Soft-deleted role without bindings - can be permanently removed and recreated + key := &table.OrgUnitCustomRoleKey{ + Tenant: authInfo.Realm, + OrgUnitId: req.Ou, + Name: req.Name, + } + err = s.customRoleTable.PermanentDelete(ctx, key) + if err != nil { + log.Printf("failed to permanently delete orphaned role %s: %s", req.Name, err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + } else if !errors.IsNotFound(err) { + log.Printf("failed to check existing role %s: %s", req.Name, err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + + // Convert protobuf permissions to table permissions + permissions := s.convertProtoPermissionsToTable(req.Permissions) + + // Create the custom role entry + customRole := &table.OrgUnitCustomRole{ + Key: &table.OrgUnitCustomRoleKey{ + Tenant: authInfo.Realm, + OrgUnitId: req.Ou, + Name: req.Name, + }, + Description: req.Description, + Permissions: permissions, + Created: time.Now().Unix(), + CreatedBy: authInfo.UserName, + Active: &[]bool{true}[0], + } + + // Insert the custom role into the database + err = s.customRoleTable.Insert(ctx, customRole.Key, customRole) + if err != nil { + if errors.IsAlreadyExists(err) { + return nil, status.Errorf(codes.AlreadyExists, "Custom role '%s' already exists in organization unit", req.Name) + } + log.Printf("failed to create custom role: %s", err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + + return &api.CreateCustomRoleResp{}, nil +} + +// UpdateCustomRole updates an existing custom role +func (s *OrgUnitRoleServer) UpdateCustomRole(ctx context.Context, req *api.UpdateCustomRoleReq) (*api.UpdateCustomRoleResp, error) { + authInfo, _ := auth.GetAuthInfoFromContext(ctx) + if authInfo == nil { + return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") + } + + if err := s.validateUpdateCustomRoleRequest(req); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "Invalid request: %s", err) + } + + permissions := s.convertProtoPermissionsToTable(req.Permissions) + + updateRole := &table.OrgUnitCustomRole{ + Description: req.Description, + Permissions: permissions, + Updated: time.Now().Unix(), + UpdatedBy: authInfo.UserName, + } + + key := &table.OrgUnitCustomRoleKey{ + Tenant: authInfo.Realm, + OrgUnitId: req.Ou, + Name: req.Name, + } + + err := s.customRoleTable.Update(ctx, key, updateRole) + if err != nil { + if errors.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, "Custom role '%s' not found in organization unit", req.Name) + } + log.Printf("failed to update custom role: %s", err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + + return &api.UpdateCustomRoleResp{}, nil +} + +// GetCustomRole retrieves details of a specific custom role +func (s *OrgUnitRoleServer) GetCustomRole(ctx context.Context, req *api.GetCustomRoleReq) (*api.GetCustomRoleResp, error) { + authInfo, _ := auth.GetAuthInfoFromContext(ctx) + if authInfo == nil { + return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") + } + + customRole, err := s.customRoleTable.FindByNameAndOrgUnit(ctx, authInfo.Realm, req.Ou, req.Name) + if err != nil { + if errors.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, "Custom role '%s' not found in organization unit", req.Name) + } + log.Printf("failed to get custom role: %s", err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + + permissions := s.convertTablePermissionsToProto(customRole.Permissions) + + return &api.GetCustomRoleResp{ + Name: customRole.Key.Name, + Description: customRole.Description, + Permissions: permissions, + Created: customRole.Created, + CreatedBy: customRole.CreatedBy, + Updated: customRole.Updated, + UpdatedBy: customRole.UpdatedBy, + }, nil +} + +// DeleteCustomRole deletes a custom role from the organization unit +func (s *OrgUnitRoleServer) DeleteCustomRole(ctx context.Context, req *api.DeleteCustomRoleReq) (*api.DeleteCustomRoleResp, error) { + authInfo, _ := auth.GetAuthInfoFromContext(ctx) + if authInfo == nil { + return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") + } + + key := &table.OrgUnitCustomRoleKey{ + Tenant: authInfo.Realm, + OrgUnitId: req.Ou, + Name: req.Name, + } + + err := s.customRoleTable.DeleteCustomRoleWithBindingCheck(ctx, key, authInfo.UserName) + if err != nil { + if errors.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, "Custom role '%s' not found in organization unit", req.Name) + } + log.Printf("failed to delete custom role: %s", err) + return nil, status.Errorf(codes.Internal, "Something went wrong, please try again later") + } + + return &api.DeleteCustomRoleResp{ + Message: "Custom role deleted successfully", + }, nil +} + +// convertProtoResourceMatchCriteriaToTable converts protobuf enum to table string constant +func (s *OrgUnitRoleServer) convertProtoResourceMatchCriteriaToTable(criteria api.ResourceMatchCriteriaDefs_Criteria) table.ResourceMatchCriteria { + switch criteria { + case api.ResourceMatchCriteriaDefs_Any: + return table.ResourceMatchCriteriaWildcard + case api.ResourceMatchCriteriaDefs_Exact: + return table.ResourceMatchCriteriaExact + case api.ResourceMatchCriteriaDefs_Prefix: + return table.ResourceMatchCriteriaPrefix + case api.ResourceMatchCriteriaDefs_Suffix: + return table.ResourceMatchCriteriaSuffix + case api.ResourceMatchCriteriaDefs_Regex: + return table.ResourceMatchCriteriaRegex + default: + // Unknown/Unspecified criteria - default to Wildcard (show all) + log.Printf("Unknown or unspecified resource match criteria: %v, defaulting to Wildcard (show all)", criteria) + return table.ResourceMatchCriteriaWildcard + } +} + +// convertTableResourceMatchCriteriaToProto converts table string constant to protobuf enum +func (s *OrgUnitRoleServer) convertTableResourceMatchCriteriaToProto(criteria table.ResourceMatchCriteria) api.ResourceMatchCriteriaDefs_Criteria { + switch criteria { + case table.ResourceMatchCriteriaExact: + return api.ResourceMatchCriteriaDefs_Exact + case table.ResourceMatchCriteriaPrefix: + return api.ResourceMatchCriteriaDefs_Prefix + case table.ResourceMatchCriteriaSuffix: + return api.ResourceMatchCriteriaDefs_Suffix + case table.ResourceMatchCriteriaRegex: + return api.ResourceMatchCriteriaDefs_Regex + case table.ResourceMatchCriteriaWildcard: + return api.ResourceMatchCriteriaDefs_Any + case table.ResourceMatchCriteriaUnspecified: + // UNSPECIFIED in database - default to Any (show all) + log.Printf("Unspecified resource match criteria in database, defaulting to Any") + return api.ResourceMatchCriteriaDefs_Any + default: + log.Printf("Unknown table resource match criteria: %v, defaulting to Any", criteria) + return api.ResourceMatchCriteriaDefs_Any + } +} + +// convertProtoActionToTable converts protobuf enum to table string constant +func (s *OrgUnitRoleServer) convertProtoActionToTable(action api.RolePermissionActionDefs_Action) table.RolePermissionAction { + switch action { + case api.RolePermissionActionDefs_Allow: + return table.RolePermissionActionAllow + case api.RolePermissionActionDefs_Deny: + return table.RolePermissionActionDeny + case api.RolePermissionActionDefs_Log: + return table.RolePermissionActionLog + case api.RolePermissionActionDefs_Unspecified: + // UNSPECIFIED action - default to Deny for security + log.Printf("Unspecified action encountered, defaulting to Deny") + return table.RolePermissionActionDeny + default: + log.Printf("Unknown action: %v, defaulting to Deny", action) + return table.RolePermissionActionDeny + } +} + +// convertTableActionToProto converts table string constant to protobuf enum +func (s *OrgUnitRoleServer) convertTableActionToProto(action table.RolePermissionAction) api.RolePermissionActionDefs_Action { + switch action { + case table.RolePermissionActionAllow: + return api.RolePermissionActionDefs_Allow + case table.RolePermissionActionDeny: + return api.RolePermissionActionDefs_Deny + case table.RolePermissionActionLog: + return api.RolePermissionActionDefs_Log + case table.RolePermissionActionUnspecified: + // UNSPECIFIED in database - default to Deny for security + log.Printf("Unspecified action in database, defaulting to Deny") + return api.RolePermissionActionDefs_Deny + default: + log.Printf("Unknown table action: %v, defaulting to Deny", action) + return api.RolePermissionActionDefs_Deny + } +} + +// convertProtoPermissionsToTable converts protobuf RolePermission slice to table RolePermission slice +func (s *OrgUnitRoleServer) convertProtoPermissionsToTable(protoPerms []*api.RolePermission) []*table.RolePermission { + var permissions []*table.RolePermission + for _, perm := range protoPerms { + var match *table.ResourceMatch + if perm.Match != nil { + match = &table.ResourceMatch{ + Criteria: s.convertProtoResourceMatchCriteriaToTable(perm.Match.Criteria), + Key: perm.Match.Key, + } + } + + permissions = append(permissions, &table.RolePermission{ + Resource: perm.Resource, + Match: match, + Verbs: perm.Verbs, + Action: s.convertProtoActionToTable(perm.Action), + }) + } + return permissions +} + +// convertTablePermissionsToProto converts table RolePermission slice to protobuf RolePermission slice +func (s *OrgUnitRoleServer) convertTablePermissionsToProto(tablePerms []*table.RolePermission) []*api.RolePermission { + var permissions []*api.RolePermission + for _, perm := range tablePerms { + var match *api.ResourceMatch + if perm.Match != nil { + match = &api.ResourceMatch{ + Criteria: s.convertTableResourceMatchCriteriaToProto(perm.Match.Criteria), + Key: perm.Match.Key, + } + } + + permissions = append(permissions, &api.RolePermission{ + Resource: perm.Resource, + Match: match, + Verbs: perm.Verbs, + Action: s.convertTableActionToProto(perm.Action), + }) + } + return permissions +} + +// validateCreateCustomRoleRequest validates the CreateCustomRole request fields +func (s *OrgUnitRoleServer) validateCreateCustomRoleRequest(req *api.CreateCustomRoleReq) error { + if req.Name == "" { + return errors.New("role name cannot be empty") + } + if len(req.Name) < 2 { + return errors.New("role name must be at least 2 characters long") + } + if len(req.Name) > 50 { + return errors.New("role name cannot exceed 50 characters") + } + + // Description is optional, but if provided, validate length + if len(req.Description) > 500 { + return errors.New("description cannot exceed 500 characters") + } + + if len(req.Permissions) == 0 { + return errors.New("at least one permission must be specified") + } + if len(req.Permissions) > 20 { + return errors.New("cannot have more than 20 permissions per role") + } + + for i, perm := range req.Permissions { + if perm.Resource == "" { + return fmt.Errorf("permission %d: resource cannot be empty", i+1) + } + if len(perm.Verbs) == 0 { + return fmt.Errorf("permission %d: at least one verb must be specified", i+1) + } + for j, verb := range perm.Verbs { + if verb == "" { + return fmt.Errorf("permission %d, verb %d: verb cannot be empty", i+1, j+1) + } + } + if perm.Action == api.RolePermissionActionDefs_Unspecified { + return fmt.Errorf("permission %d: action must be specified (Allow, Deny, or Log)", i+1) + } + if perm.Action != api.RolePermissionActionDefs_Allow && + perm.Action != api.RolePermissionActionDefs_Deny && + perm.Action != api.RolePermissionActionDefs_Log { + return fmt.Errorf("permission %d: invalid action value: %v", i+1, perm.Action) + } + } + + return nil +} + +// validateUpdateCustomRoleRequest validates the UpdateCustomRole request fields +func (s *OrgUnitRoleServer) validateUpdateCustomRoleRequest(req *api.UpdateCustomRoleReq) error { + // Description is optional, but if provided, validate length + if len(req.Description) > 500 { + return errors.New("description cannot exceed 500 characters") + } + + if len(req.Permissions) == 0 { + return errors.New("at least one permission must be specified") + } + if len(req.Permissions) > 20 { + return errors.New("cannot have more than 20 permissions per role") + } + + for i, perm := range req.Permissions { + if perm.Resource == "" { + return fmt.Errorf("permission %d: resource cannot be empty", i+1) + } + if len(perm.Verbs) == 0 { + return fmt.Errorf("permission %d: at least one verb must be specified", i+1) + } + for j, verb := range perm.Verbs { + if verb == "" { + return fmt.Errorf("permission %d, verb %d: verb cannot be empty", i+1, j+1) + } + } + if perm.Action == api.RolePermissionActionDefs_Unspecified { + return fmt.Errorf("permission %d: action must be specified (Allow, Deny, or Log)", i+1) + } + if perm.Action != api.RolePermissionActionDefs_Allow && + perm.Action != api.RolePermissionActionDefs_Deny && + perm.Action != api.RolePermissionActionDefs_Log { + return fmt.Errorf("permission %d: invalid action value: %v", i+1, perm.Action) + } + } + + return nil +} + func NewOrgUnitRoleServer(ctx *model.GrpcServerContext, ep string) *OrgUnitRoleServer { - srv := &OrgUnitRoleServer{} + customRoleTable, err := table.GetOrgUnitCustomRoleTable() + if err != nil { + log.Panicf("failed to get org unit custom role table: %s", err) + } + + srv := &OrgUnitRoleServer{ + customRoleTable: customRoleTable, + } api.RegisterOrgUnitRoleServer(ctx.Server, srv) - err := api.RegisterOrgUnitRoleHandler(context.Background(), ctx.Mux, ctx.Conn) + err = api.RegisterOrgUnitRoleHandler(context.Background(), ctx.Mux, ctx.Conn) if err != nil { log.Panicf("failed to register handler: %s", err) } diff --git a/pkg/server/org-unit-user.go b/pkg/server/org-unit-user.go index b9795a5..75248e0 100644 --- a/pkg/server/org-unit-user.go +++ b/pkg/server/org-unit-user.go @@ -22,7 +22,28 @@ import ( type OrgUnitUserServer struct { api.UnimplementedOrgUnitUserServer - tbl *table.OrgUnitUserTable + tbl *table.OrgUnitUserTable + customRoleTable *table.OrgUnitCustomRoleTable +} + +// isValidRole checks if a role is valid (either built-in or custom) +func (s *OrgUnitUserServer) isValidRole(ctx context.Context, tenant, orgUnitId, role string) error { + // Check if it's a built-in role + if role == "admin" || role == "auditor" { + return nil + } + + // Check if it's a valid custom role + _, err := s.customRoleTable.FindByNameAndOrgUnit(ctx, tenant, orgUnitId, role) + if err != nil { + if errors.IsNotFound(err) { + return status.Errorf(codes.InvalidArgument, "Invalid role: '%s'. Must be 'admin', 'auditor', or a valid custom role", role) + } + log.Printf("failed to check custom role existence: %s", err) + return status.Errorf(codes.Internal, "Failed to validate role") + } + + return nil } func (s *OrgUnitUserServer) ListOrgUnitUsers(ctx context.Context, req *api.OrgUnitUsersListReq) (*api.OrgUnitUsersListResp, error) { @@ -62,9 +83,10 @@ func (s *OrgUnitUserServer) AddOrgUnitUser(ctx context.Context, req *api.OrgUnit return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") } - // validate role, currently only admin, default and auditor roles are allowed - if req.Role != "admin" && req.Role != "default" && req.Role != "auditor" { - return nil, status.Errorf(codes.InvalidArgument, "Invalid role: %s", req.Role) + // Validate role (built-in or custom) + err := s.isValidRole(ctx, authInfo.Realm, req.Ou, req.Role) + if err != nil { + return nil, err } // TODO: validate if user exists, this might be never done to allow adding users @@ -81,7 +103,7 @@ func (s *OrgUnitUserServer) AddOrgUnitUser(ctx context.Context, req *api.OrgUnit Role: req.Role, } - err := s.tbl.Insert(ctx, entry.Key, entry) + err = s.tbl.Insert(ctx, entry.Key, entry) if err != nil { if errors.IsAlreadyExists(err) { return nil, status.Errorf(codes.AlreadyExists, "Org Unit User %s, already exists", req.User) @@ -99,9 +121,10 @@ func (s *OrgUnitUserServer) UpdateOrgUnitUser(ctx context.Context, req *api.OrgU return nil, status.Errorf(codes.Unauthenticated, "User not authenticated") } - // validate role, currently only admin, default and auditor roles are allowed - if req.Role != "admin" && req.Role != "default" && req.Role != "auditor" { - return nil, status.Errorf(codes.InvalidArgument, "Invalid role: %s", req.Role) + // Validate role (built-in or custom) + err := s.isValidRole(ctx, authInfo.Realm, req.Ou, req.Role) + if err != nil { + return nil, err } update := &table.OrgUnitUser{ @@ -113,7 +136,7 @@ func (s *OrgUnitUserServer) UpdateOrgUnitUser(ctx context.Context, req *api.OrgU Role: req.Role, } - err := s.tbl.Update(ctx, update.Key, update) + err = s.tbl.Update(ctx, update.Key, update) if err != nil { if errors.IsNotFound(err) { return nil, status.Errorf(codes.NotFound, "Org Unit User %s, not found", req.User) @@ -153,8 +176,15 @@ func NewOrgUnitUserServer(ctx *model.GrpcServerContext, ep string) *OrgUnitUserS if err != nil { log.Panicf("failed to get org unit user table: %s", err) } + + customRoleTable, err := table.GetOrgUnitCustomRoleTable() + if err != nil { + log.Panicf("failed to get org unit custom role table: %s", err) + } + srv := &OrgUnitUserServer{ - tbl: tbl, + tbl: tbl, + customRoleTable: customRoleTable, } api.RegisterOrgUnitUserServer(ctx.Server, srv) err = api.RegisterOrgUnitUserHandler(context.Background(), ctx.Mux, ctx.Conn) diff --git a/pkg/table/const.go b/pkg/table/const.go index a06406f..0aa1a0f 100644 --- a/pkg/table/const.go +++ b/pkg/table/const.go @@ -30,6 +30,9 @@ const ( // Org Unit User collection name OrgUnitUserCollectionName = "org-unit-users" + // Org Unit Custom Role collection name + OrgUnitCustomRoleCollectionName = "org-unit-roles" + // Identity Provider collection name IdentityProviderCollectionName = "identity-providers" ) diff --git a/pkg/table/org-unit-role.go b/pkg/table/org-unit-role.go new file mode 100644 index 0000000..9f5967b --- /dev/null +++ b/pkg/table/org-unit-role.go @@ -0,0 +1,307 @@ +// Copyright © 2025 Prabhjot Singh Sethi, All Rights reserved +// Author: Suryanshu Gupta + +package table + +import ( + "context" + "time" + + "github.com/go-core-stack/core/db" + "github.com/go-core-stack/core/errors" + "github.com/go-core-stack/core/table" + "go.mongodb.org/mongo-driver/bson" +) + +var orgUnitCustomRoleTable *OrgUnitCustomRoleTable + +// OrgUnitCustomRoleKey defines the key structure for custom roles +type OrgUnitCustomRoleKey struct { + // Tenant name this custom role belongs to + Tenant string `bson:"tenant,omitempty"` + // Org unit ID this custom role is scoped to + OrgUnitId string `bson:"orgUnitId,omitempty"` + // Role name, unique within the org unit + Name string `bson:"name,omitempty"` +} + +// ResourceMatchCriteria defines allowed values for resource matching criteria +type ResourceMatchCriteria string + +const ( + ResourceMatchCriteriaUnspecified ResourceMatchCriteria = "" + ResourceMatchCriteriaExact ResourceMatchCriteria = "exact" + ResourceMatchCriteriaPrefix ResourceMatchCriteria = "prefix" + ResourceMatchCriteriaSuffix ResourceMatchCriteria = "suffix" + ResourceMatchCriteriaRegex ResourceMatchCriteria = "regex" + ResourceMatchCriteriaWildcard ResourceMatchCriteria = "wildcard" +) + +// ResourceMatch defines matching criteria for resource-based permissions +type ResourceMatch struct { + // Matching criteria type + Criteria ResourceMatchCriteria `bson:"criteria,omitempty"` + // The matching key/pattern based on criteria + Key string `bson:"key,omitempty"` +} + +// RolePermissionAction defines allowed values for role permission actions +type RolePermissionAction string + +const ( + RolePermissionActionUnspecified RolePermissionAction = "" + RolePermissionActionAllow RolePermissionAction = "Allow" + RolePermissionActionDeny RolePermissionAction = "Deny" + RolePermissionActionLog RolePermissionAction = "Log" +) + +// RolePermission defines individual permission for a resource +type RolePermission struct { + // Resource name the permission applies to + Resource string `bson:"resource,omitempty"` + // Resource matching criteria (optional, defaults to wildcard with key="*") + Match *ResourceMatch `bson:"match,omitempty"` + // List of allowed verbs/actions for this resource (supports "*" for all verbs) + Verbs []string `bson:"verbs,omitempty"` + // Action type: Allow or Deny (Deny takes precedence over Allow) + Action RolePermissionAction `bson:"action,omitempty"` +} + +// OrgUnitCustomRole defines a custom role within an organization unit +type OrgUnitCustomRole struct { + // Custom role key + Key *OrgUnitCustomRoleKey `bson:"key,omitempty"` + // Description explaining the purpose of this custom role + Description string `bson:"description,omitempty"` + // List of permissions granted by this custom role + Permissions []*RolePermission `bson:"permissions,omitempty"` + // Created timestamp + Created int64 `bson:"created,omitempty"` + // User who created this custom role + CreatedBy string `bson:"createdBy,omitempty"` + // Last updated timestamp + Updated int64 `bson:"updated,omitempty"` + // User who last updated this custom role + UpdatedBy string `bson:"updatedBy,omitempty"` + // Whether this custom role is currently active + Active *bool `bson:"active,omitempty"` +} + +// OrgUnitCustomRoleTable manages custom roles for organization units +type OrgUnitCustomRoleTable struct { + table.Table[OrgUnitCustomRoleKey, OrgUnitCustomRole] + col db.StoreCollection +} + +// GetByOrgUnit retrieves all custom roles for a specific organization unit +func (t *OrgUnitCustomRoleTable) GetByOrgUnit(ctx context.Context, tenant, orgUnitId string, offset, limit int32) ([]*OrgUnitCustomRole, error) { + filter := bson.M{ + "key.tenant": tenant, + "key.orgUnitId": orgUnitId, + "active": bson.M{"$ne": false}, // Include roles where active is true or nil + } + + list, err := t.FindMany(ctx, filter, offset, limit) + if err != nil { + return nil, err + } + + return list, nil +} + +// CountByOrgUnit counts active custom roles for a specific organization unit +func (t *OrgUnitCustomRoleTable) CountByOrgUnit(ctx context.Context, tenant, orgUnitId string) (int32, error) { + filter := bson.M{ + "key.tenant": tenant, + "key.orgUnitId": orgUnitId, + "active": bson.M{"$ne": false}, // Include roles where active is true or nil + } + count, err := t.col.Count(ctx, filter) + return int32(count), err +} + +// GetByTenant retrieves all custom roles for a tenant across all org units +func (t *OrgUnitCustomRoleTable) GetByTenant(ctx context.Context, tenant string, offset, limit int32) ([]*OrgUnitCustomRole, error) { + filter := bson.M{ + "key.tenant": tenant, + "active": bson.M{"$ne": false}, // Include roles where active is true or nil + } + + list, err := t.FindMany(ctx, filter, offset, limit) + if err != nil { + return nil, err + } + + return list, nil +} + +// FindByNameAndOrgUnit finds a specific custom role by name within an org unit +func (t *OrgUnitCustomRoleTable) FindByNameAndOrgUnit(ctx context.Context, tenant, orgUnitId, roleName string) (*OrgUnitCustomRole, error) { + filter := bson.M{ + "key.tenant": tenant, // Filter by tenant + "key.orgUnitId": orgUnitId, // Filter by organization unit + "key.name": roleName, // Filter by role name + "active": bson.M{"$ne": false}, // Include only active roles (true or nil) + } + + // Use FindMany with limit 1 to get a single active role + results, err := t.FindMany(ctx, filter, 0, 1) + if err != nil { + return nil, err + } + + if len(results) == 0 { + return nil, errors.Wrapf(errors.NotFound, "custom role not found") + } + + return results[0], nil +} + +// SoftDelete marks a custom role as inactive instead of physically deleting it +func (t *OrgUnitCustomRoleTable) SoftDelete(ctx context.Context, key *OrgUnitCustomRoleKey, deletedBy string) error { + update := &OrgUnitCustomRole{ + Active: &[]bool{false}[0], // Mark as inactive + UpdatedBy: deletedBy, // Track who performed the deletion + Updated: time.Now().Unix(), // Update timestamp + } + + return t.Update(ctx, key, update) +} + +// HasBindings checks if a custom role has any users assigned to it +func (t *OrgUnitCustomRoleTable) HasBindings(ctx context.Context, tenant, orgUnitId, roleName string) (bool, error) { + // Get the org unit user table to check for role assignments + orgUnitUserTable, err := GetOrgUnitUserTable() + if err != nil { + return false, err + } + + // Check if any users are assigned this custom role + filter := bson.M{ + "key.tenant": tenant, + "key.orgUnitId": orgUnitId, + "role": roleName, + } + + count, err := orgUnitUserTable.col.Count(ctx, filter) + if err != nil { + return false, err + } + + return count > 0, nil +} + +// PermanentDelete permanently removes a custom role from the database +func (t *OrgUnitCustomRoleTable) PermanentDelete(ctx context.Context, key *OrgUnitCustomRoleKey) error { + return t.DeleteKey(ctx, key) +} + +// DeleteCustomRoleWithBindingCheck performs intelligent deletion based on binding status +func (t *OrgUnitCustomRoleTable) DeleteCustomRoleWithBindingCheck(ctx context.Context, key *OrgUnitCustomRoleKey, deletedBy string) error { + // Check if the role has any bindings + hasBindings, err := t.HasBindings(ctx, key.Tenant, key.OrgUnitId, key.Name) + if err != nil { + return err + } + + if hasBindings { + // Role has bindings - perform soft delete only + return t.SoftDelete(ctx, key, deletedBy) + } else { + // No bindings - permanently delete the role + return t.PermanentDelete(ctx, key) + } +} + +// FindAnyByNameAndOrgUnit finds a custom role by name (including soft-deleted ones) +// This is used to check for name conflicts including soft-deleted roles with bindings +func (t *OrgUnitCustomRoleTable) FindAnyByNameAndOrgUnit(ctx context.Context, tenant, orgUnitId, roleName string) (*OrgUnitCustomRole, error) { + filter := bson.M{ + "key.tenant": tenant, // Filter by tenant + "key.orgUnitId": orgUnitId, // Filter by organization unit + "key.name": roleName, // Filter by role name + // No active filter - find both active and inactive roles + } + + // Use FindMany with limit 1 to get any role (active or inactive) + results, err := t.FindMany(ctx, filter, 0, 1) + if err != nil { + return nil, err + } + + if len(results) == 0 { + return nil, errors.Wrapf(errors.NotFound, "custom role not found") + } + + return results[0], nil +} + +// CleanupOrphanedSoftDeletedRoles removes soft-deleted roles that no longer have bindings +func (t *OrgUnitCustomRoleTable) CleanupOrphanedSoftDeletedRoles(ctx context.Context, tenant, orgUnitId string) error { + // Find all soft-deleted roles + filter := bson.M{ + "key.tenant": tenant, + "key.orgUnitId": orgUnitId, + "active": false, // Only soft-deleted roles + } + + softDeletedRoles, err := t.FindMany(ctx, filter, 0, 0) // Get all + if err != nil { + return err + } + + // Check each soft-deleted role for bindings + for _, role := range softDeletedRoles { + hasBindings, err := t.HasBindings(ctx, tenant, orgUnitId, role.Key.Name) + if err != nil { + continue // Skip on error, don't fail the entire cleanup + } + + // If no bindings, permanently delete + if !hasBindings { + err = t.PermanentDelete(ctx, role.Key) + if err != nil { + // Log error but continue with other roles + continue + } + } + } + + return nil +} + +// StartEventLogger starts the event logger for the custom role table +func (t *OrgUnitCustomRoleTable) StartEventLogger() error { + logger := db.NewEventLogger[OrgUnitCustomRoleKey, OrgUnitCustomRole](t.col, nil) + return logger.Start(context.Background()) +} + +// GetOrgUnitCustomRoleTable returns the global custom role table instance +func GetOrgUnitCustomRoleTable() (*OrgUnitCustomRoleTable, error) { + if orgUnitCustomRoleTable != nil { + return orgUnitCustomRoleTable, nil + } + + return nil, errors.Wrapf(errors.NotFound, "org unit custom role table not found") +} + +// LocateOrgUnitCustomRoleTable initializes and returns the custom role table +func LocateOrgUnitCustomRoleTable(client db.StoreClient) (*OrgUnitCustomRoleTable, error) { + if orgUnitCustomRoleTable != nil { + return orgUnitCustomRoleTable, nil + } + + col := client.GetCollection(AuthDatabaseName, OrgUnitCustomRoleCollectionName) + tbl := &OrgUnitCustomRoleTable{ + col: col, + } + + err := tbl.Initialize(col) + if err != nil { + return nil, err + } + + orgUnitCustomRoleTable = tbl + + return orgUnitCustomRoleTable, nil +}