access.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package sharedspace
  2. /*
  3. SharedSpace access control
  4. Spaces carry an access mode (open / public / private) and a member
  5. list with roles (owner / admin / member):
  6. - CanRead: open and public spaces are readable by anyone who can
  7. name them; private spaces only by members.
  8. - CanPost: same as CanRead - reading a space implies being allowed
  9. to contribute to it (the transports decide who may name a space).
  10. - CanManage: owner and space admins - membership, metadata, access
  11. mode, deletion.
  12. An empty username is the system authority (internal callers) and
  13. passes every check. The package never authenticates: transports
  14. (prouter endpoints, the AGI gateway) hand in validated usernames.
  15. */
  16. import "sort"
  17. // AccessMode returns the space's current access mode.
  18. func (s *Space) AccessMode() string {
  19. s.mu.Lock()
  20. defer s.mu.Unlock()
  21. return s.access
  22. }
  23. // Role returns the member role of username in the space and whether the
  24. // user is a member at all. The owner always reports RoleOwner.
  25. func (s *Space) Role(username string) (string, bool) {
  26. s.mu.Lock()
  27. defer s.mu.Unlock()
  28. role, ok := s.members[username]
  29. return role, ok
  30. }
  31. // CanRead reports whether username may read the space's content.
  32. func (s *Space) CanRead(username string) bool {
  33. if username == "" {
  34. return true //system authority
  35. }
  36. s.mu.Lock()
  37. defer s.mu.Unlock()
  38. if s.access != AccessPrivate {
  39. return true
  40. }
  41. _, member := s.members[username]
  42. return member
  43. }
  44. // CanPost reports whether username may post items / documents into the
  45. // space. Posting rights follow reading rights.
  46. func (s *Space) CanPost(username string) bool {
  47. return s.CanRead(username)
  48. }
  49. // CanManage reports whether username may administer the space (members,
  50. // metadata, access mode, deletion).
  51. func (s *Space) CanManage(username string) bool {
  52. if username == "" {
  53. return true //system authority
  54. }
  55. s.mu.Lock()
  56. defer s.mu.Unlock()
  57. if username == s.Owner {
  58. return true
  59. }
  60. return s.members[username] == RoleAdmin
  61. }
  62. // Members returns a snapshot copy of the member list (username -> role).
  63. func (s *Space) Members() map[string]string {
  64. s.mu.Lock()
  65. defer s.mu.Unlock()
  66. snapshot := make(map[string]string, len(s.members))
  67. for username, role := range s.members {
  68. snapshot[username] = role
  69. }
  70. return snapshot
  71. }
  72. // MemberCount returns the number of members in the space.
  73. func (s *Space) MemberCount() int {
  74. s.mu.Lock()
  75. defer s.mu.Unlock()
  76. return len(s.members)
  77. }
  78. // AddMember invites username into the space with the given role (RoleAdmin
  79. // or RoleMember). Only the owner, a space admin or the system may invite.
  80. func (s *Space) AddMember(requester string, username string, role string) error {
  81. if role != RoleAdmin && role != RoleMember {
  82. return ErrInvalidRole
  83. }
  84. if !s.CanManage(requester) {
  85. return ErrPermissionDenied
  86. }
  87. if username == "" {
  88. return ErrInvalidRole
  89. }
  90. s.mu.Lock()
  91. if s.closed {
  92. s.mu.Unlock()
  93. return ErrSpaceClosed
  94. }
  95. if _, exists := s.members[username]; exists {
  96. s.mu.Unlock()
  97. return ErrMemberExists
  98. }
  99. if len(s.members) >= maxMembers {
  100. s.mu.Unlock()
  101. return ErrSpaceMemberLimit
  102. }
  103. s.members[username] = role
  104. s.mu.Unlock()
  105. s.mgrPersistSpace()
  106. s.emitEvent(&SpaceEvent{Kind: EventMemberChanged, Member: username, Role: role, Action: "add"})
  107. return nil
  108. }
  109. // RemoveMember removes username from the space. Managers may remove anyone
  110. // but the owner; every member may remove themselves (leave).
  111. func (s *Space) RemoveMember(requester string, username string) error {
  112. if username == s.Owner {
  113. return ErrPermissionDenied //the owner cannot be removed
  114. }
  115. if requester != username && !s.CanManage(requester) {
  116. return ErrPermissionDenied
  117. }
  118. s.mu.Lock()
  119. if _, exists := s.members[username]; !exists {
  120. s.mu.Unlock()
  121. return ErrNotMember
  122. }
  123. delete(s.members, username)
  124. s.mu.Unlock()
  125. s.mgrPersistSpace()
  126. s.emitEvent(&SpaceEvent{Kind: EventMemberChanged, Member: username, Action: "remove"})
  127. return nil
  128. }
  129. // SetMemberRole changes an existing member's role (RoleAdmin or RoleMember).
  130. // The owner's role cannot be changed.
  131. func (s *Space) SetMemberRole(requester string, username string, role string) error {
  132. if role != RoleAdmin && role != RoleMember {
  133. return ErrInvalidRole
  134. }
  135. if username == s.Owner {
  136. return ErrPermissionDenied
  137. }
  138. if !s.CanManage(requester) {
  139. return ErrPermissionDenied
  140. }
  141. s.mu.Lock()
  142. if _, exists := s.members[username]; !exists {
  143. s.mu.Unlock()
  144. return ErrNotMember
  145. }
  146. s.members[username] = role
  147. s.mu.Unlock()
  148. s.mgrPersistSpace()
  149. s.emitEvent(&SpaceEvent{Kind: EventMemberChanged, Member: username, Role: role, Action: "role"})
  150. return nil
  151. }
  152. // JoinPublic self-joins username into a public or open space as RoleMember.
  153. // Open spaces allow joining so the space shows up in the user's joined
  154. // list; private spaces reject self-joins (invite only).
  155. func (s *Space) JoinPublic(username string) error {
  156. if username == "" {
  157. return ErrPermissionDenied
  158. }
  159. s.mu.Lock()
  160. if s.closed {
  161. s.mu.Unlock()
  162. return ErrSpaceClosed
  163. }
  164. if s.access == AccessPrivate {
  165. s.mu.Unlock()
  166. return ErrPermissionDenied
  167. }
  168. if _, exists := s.members[username]; exists {
  169. s.mu.Unlock()
  170. return ErrMemberExists
  171. }
  172. if len(s.members) >= maxMembers {
  173. s.mu.Unlock()
  174. return ErrSpaceMemberLimit
  175. }
  176. s.members[username] = RoleMember
  177. s.mu.Unlock()
  178. s.mgrPersistSpace()
  179. s.emitEvent(&SpaceEvent{Kind: EventMemberChanged, Member: username, Role: RoleMember, Action: "add"})
  180. return nil
  181. }
  182. // SetAccess changes the space's access mode. Managers only.
  183. func (s *Space) SetAccess(requester string, access string) error {
  184. if !validAccessMode(access) {
  185. return ErrInvalidAccess
  186. }
  187. if !s.CanManage(requester) {
  188. return ErrPermissionDenied
  189. }
  190. s.mu.Lock()
  191. s.access = access
  192. s.mu.Unlock()
  193. s.mgrPersistSpace()
  194. return nil
  195. }
  196. // SetMeta sets (or, with an empty value, deletes) a metadata entry on the
  197. // space. Managers only. Keys and values are length-capped and the total
  198. // number of entries is limited.
  199. func (s *Space) SetMeta(requester string, key string, value string) error {
  200. if !s.CanManage(requester) {
  201. return ErrPermissionDenied
  202. }
  203. key = clipString(key, maxMetadataKeyLen)
  204. if key == "" {
  205. return ErrPermissionDenied
  206. }
  207. value = clipString(value, maxMetadataValLen)
  208. s.mu.Lock()
  209. if value == "" {
  210. delete(s.metadata, key)
  211. } else {
  212. if _, exists := s.metadata[key]; !exists && len(s.metadata) >= maxMetadataKeys {
  213. s.mu.Unlock()
  214. return ErrMetadataLimit
  215. }
  216. s.metadata[key] = value
  217. }
  218. s.mu.Unlock()
  219. s.mgrPersistSpace()
  220. return nil
  221. }
  222. // Metadata returns a snapshot copy of the space's metadata.
  223. func (s *Space) Metadata() map[string]string {
  224. s.mu.Lock()
  225. defer s.mu.Unlock()
  226. snapshot := make(map[string]string, len(s.metadata))
  227. for key, value := range s.metadata {
  228. snapshot[key] = value
  229. }
  230. return snapshot
  231. }
  232. // sanitizeMetadata validates and clips an initial metadata map.
  233. func sanitizeMetadata(meta map[string]string) map[string]string {
  234. clean := make(map[string]string)
  235. if meta == nil {
  236. return clean
  237. }
  238. //Deterministic pick order when the input exceeds the entry cap
  239. keys := make([]string, 0, len(meta))
  240. for key := range meta {
  241. keys = append(keys, key)
  242. }
  243. sort.Strings(keys)
  244. for _, key := range keys {
  245. if len(clean) >= maxMetadataKeys {
  246. break
  247. }
  248. clippedKey := clipString(key, maxMetadataKeyLen)
  249. if clippedKey == "" || meta[key] == "" {
  250. continue
  251. }
  252. clean[clippedKey] = clipString(meta[key], maxMetadataValLen)
  253. }
  254. return clean
  255. }
  256. // ListPublicSpaces returns a snapshot of every space with AccessPublic.
  257. func (m *Manager) ListPublicSpaces() []*Space {
  258. m.mu.RLock()
  259. defer m.mu.RUnlock()
  260. public := []*Space{}
  261. for _, space := range m.spaces {
  262. space.mu.Lock()
  263. isPublic := space.access == AccessPublic
  264. space.mu.Unlock()
  265. if isPublic {
  266. public = append(public, space)
  267. }
  268. }
  269. return public
  270. }
  271. // ListSpacesByMember returns a snapshot of the spaces username belongs to
  272. // (as owner, admin or member).
  273. func (m *Manager) ListSpacesByMember(username string) []*Space {
  274. m.mu.RLock()
  275. defer m.mu.RUnlock()
  276. joined := []*Space{}
  277. for _, space := range m.spaces {
  278. if _, member := space.Role(username); member {
  279. joined = append(joined, space)
  280. }
  281. }
  282. return joined
  283. }
  284. // ListSpaces returns a snapshot of every live space (administrator surface).
  285. func (m *Manager) ListSpaces() []*Space {
  286. m.mu.RLock()
  287. defer m.mu.RUnlock()
  288. all := make([]*Space, 0, len(m.spaces))
  289. for _, space := range m.spaces {
  290. all = append(all, space)
  291. }
  292. return all
  293. }
  294. // SpaceDiskUsage returns the approximate bytes a space occupies (item sizes
  295. // plus document contents), computed from in-memory state.
  296. func (m *Manager) SpaceDiskUsage(spaceID string) int64 {
  297. space, ok := m.GetSpace(spaceID)
  298. if !ok {
  299. return 0
  300. }
  301. space.mu.Lock()
  302. defer space.mu.Unlock()
  303. var total int64
  304. for _, item := range space.items {
  305. total += item.Size
  306. }
  307. for _, doc := range space.docs {
  308. total += int64(len(doc.content))
  309. }
  310. return total
  311. }