go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/realms/permissions.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package realms 16 17 import ( 18 "strings" 19 "sync" 20 21 "go.chromium.org/luci/common/errors" 22 ) 23 24 var ( 25 mu sync.RWMutex 26 perms = map[string]PermissionFlags{} 27 forbidChanges bool 28 ) 29 30 // PermissionFlags describe how a registered permission is going to be used in 31 // the current process. 32 // 33 // These are purely process-local flags. It is possible for the same single 34 // permission to be defined with different flags in different processes. 35 type PermissionFlags uint8 36 37 const ( 38 // UsedInQueryRealms indicates the permission will be used with QueryRealms. 39 // 40 // It instructs the runtime to build an in-memory index to speed up the query. 41 // This flag is necessary for QueryRealms function to work. It implies some 42 // performance and memory costs and should be used only with permissions that 43 // are going to be passed to QueryRealms. 44 UsedInQueryRealms PermissionFlags = 1 << iota 45 ) 46 47 // Permission is a symbol that has form "<service>.<subject>.<verb>", which 48 // describes some elementary action ("<verb>") that can be done to some category 49 // of resources ("<subject>"), managed by some particular kind of LUCI service 50 // ("<service>"). 51 // 52 // Each individual LUCI service should document what permissions it checks and 53 // when. It becomes a part of service's public API. Usually services should 54 // check only permissions of resources they own (e.g. "<service>.<subject>.*"), 55 // but in exceptional cases they may also check permissions intended for other 56 // services. This is primarily useful for services that somehow "proxy" access 57 // to resources. 58 type Permission struct { 59 name string 60 } 61 62 // Name is "<service>.<subject>.<verb>" string. 63 func (p Permission) Name() string { 64 return p.name 65 } 66 67 // String allows permissions to be used as "%s" in format strings. 68 func (p Permission) String() string { 69 return p.name 70 } 71 72 // AddFlags appends given flags to a registered permission. 73 // 74 // Permissions are usually defined in shared packages used by multiple services. 75 // Flags that makes sense for one service do not always make sense for another. 76 // For that reason RegisterPermission and AddFlags are two separate functions. 77 // 78 // Must to be called during startup, e.g. in init(). Panics if called when 79 // the process is already serving requests. 80 func (p Permission) AddFlags(flags PermissionFlags) { 81 mu.Lock() 82 defer mu.Unlock() 83 if forbidChanges { 84 panic("cannot call Permission.AddFlags while already serving requests, do it before starting the serving loop, e.g. in init()") 85 } 86 perms[p.name] |= flags 87 } 88 89 // clearPermissions removes all registered permissions (for tests). 90 func clearPermissions() { 91 mu.Lock() 92 perms = map[string]PermissionFlags{} 93 forbidChanges = false 94 mu.Unlock() 95 } 96 97 // RegisterPermission adds a new permission with the given name to the process 98 // registry or returns an existing one. 99 // 100 // Panics if the permission name doesn't look like "<service>.<subject>.<verb>". 101 // 102 // Must to be called during startup, e.g. in init(). Panics if called when 103 // the process is already serving requests. 104 func RegisterPermission(name string) Permission { 105 mu.Lock() 106 defer mu.Unlock() 107 if forbidChanges { 108 panic("cannot call RegisterPermission while already serving requests, do it before starting the serving loop, e.g. in init()") 109 } 110 if err := ValidatePermissionName(name); err != nil { 111 panic(err) 112 } 113 perms[name] |= 0 114 return Permission{name: name} 115 } 116 117 // GetPermissions returns the permissions with the matching names. The order of 118 // the permission is the same as the provided names. 119 // 120 // Implicitly calls ForbidPermissionChanges to make sure no new permissions 121 // are added later. 122 // 123 // Returns an error if any of the permission isn't registered. 124 func GetPermissions(names ...string) ([]Permission, error) { 125 mu.RLock() 126 var err error 127 for _, name := range names { 128 if _, ok := perms[name]; !ok { 129 err = errors.Reason("permission not registered: %q", name).Err() 130 break 131 } 132 } 133 forbiddenAlready := forbidChanges 134 mu.RUnlock() 135 136 if err != nil { 137 return nil, err 138 } 139 140 if !forbiddenAlready { 141 mu.Lock() 142 forbidChanges = true 143 mu.Unlock() 144 } 145 146 perms := make([]Permission, 0, len(names)) 147 for _, name := range names { 148 perms = append(perms, Permission{name}) 149 } 150 return perms, nil 151 } 152 153 // RegisteredPermissions returns a snapshot of all registered permissions along 154 // with their flags. 155 // 156 // Implicitly calls ForbidPermissionChanges to make sure no new permissions 157 // are added later. 158 func RegisteredPermissions() map[Permission]PermissionFlags { 159 mu.RLock() 160 all := make(map[Permission]PermissionFlags, len(perms)) 161 for name, flags := range perms { 162 all[Permission{name: name}] = flags 163 } 164 forbiddenAlready := forbidChanges 165 mu.RUnlock() 166 167 if !forbiddenAlready { 168 mu.Lock() 169 forbidChanges = true 170 mu.Unlock() 171 } 172 173 return all 174 } 175 176 // ForbidPermissionChanges explicitly forbids registering new permissions or 177 // changing their flags. 178 // 179 // All permissions should be registered before the server starts running its 180 // loop. The runtime relies on this when building various caches. After this 181 // function is called, RegisterPermissions and AddFlags would start panicking. 182 // 183 // Intended for internal server code. 184 func ForbidPermissionChanges() { 185 mu.Lock() 186 forbidChanges = true 187 mu.Unlock() 188 } 189 190 // ValidatePermissionName returns an error if the permission name is invalid. 191 // 192 // It checks the name looks like "<service>.<subject>.<verb>". 193 func ValidatePermissionName(name string) error { 194 if parts := strings.Split(name, "."); len(parts) == 3 { 195 good := true 196 for _, p := range parts { 197 if p == "" { 198 good = false 199 break 200 } 201 } 202 if good { 203 return nil 204 } 205 } 206 return errors.Reason("bad permission %q - must have form <service>.<subject>.<verb>", name).Err() 207 }