zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/requestcontext/user_access_control.go (about) 1 package uac 2 3 import ( 4 "context" 5 "net/http" 6 7 glob "github.com/bmatcuk/doublestar/v4" //nolint:gci 8 9 "zotregistry.dev/zot/errors" 10 "zotregistry.dev/zot/pkg/api/constants" 11 ) 12 13 type Key int 14 15 // request-local context key. 16 var uacCtxKey = Key(0) //nolint: gochecknoglobals 17 18 // pointer needed for use in context.WithValue. 19 func GetContextKey() *Key { 20 return &uacCtxKey 21 } 22 23 type UserAccessControl struct { 24 authzInfo *UserAuthzInfo 25 authnInfo *UserAuthnInfo 26 methodActions []string 27 behaviourActions []string 28 } 29 30 type UserAuthzInfo struct { 31 // {action: {repo: bool}} 32 globPatterns map[string]map[string]bool 33 isAdmin bool 34 } 35 36 type UserAuthnInfo struct { 37 groups []string 38 username string 39 } 40 41 func NewUserAccessControl() *UserAccessControl { 42 return &UserAccessControl{ 43 // authzInfo will be populated in authz.go middleware 44 // if no authz enabled on server this will be nil 45 authzInfo: nil, 46 // authnInfo will be populated in authn.go middleware 47 // if no authn enabled on server this will be nil 48 authnInfo: nil, 49 // actions type 50 behaviourActions: []string{constants.DetectManifestCollisionPermission}, 51 methodActions: []string{ 52 constants.ReadPermission, 53 constants.CreatePermission, 54 constants.UpdatePermission, 55 constants.DeletePermission, 56 }, 57 } 58 } 59 60 func (uac *UserAccessControl) SetUsername(username string) { 61 if uac.authnInfo == nil { 62 uac.authnInfo = &UserAuthnInfo{} 63 } 64 65 uac.authnInfo.username = username 66 } 67 68 func (uac *UserAccessControl) GetUsername() string { 69 if uac.authnInfo == nil { 70 return "" 71 } 72 73 return uac.authnInfo.username 74 } 75 76 func (uac *UserAccessControl) AddGroups(groups []string) { 77 if uac.authnInfo == nil { 78 uac.authnInfo = &UserAuthnInfo{ 79 groups: []string{}, 80 } 81 } 82 83 uac.authnInfo.groups = append(uac.authnInfo.groups, groups...) 84 } 85 86 func (uac *UserAccessControl) GetGroups() []string { 87 if uac.authnInfo == nil { 88 return []string{} 89 } 90 91 return uac.authnInfo.groups 92 } 93 94 func (uac *UserAccessControl) IsAnonymous() bool { 95 if uac.authnInfo == nil { 96 return true 97 } 98 99 return uac.authnInfo.username == "" 100 } 101 102 func (uac *UserAccessControl) IsAdmin() bool { 103 // if isAdmin was not set in authz.go then everybody is admin 104 if uac.authzInfo == nil { 105 return true 106 } 107 108 return uac.authzInfo.isAdmin 109 } 110 111 func (uac *UserAccessControl) SetIsAdmin(isAdmin bool) { 112 if uac.authzInfo == nil { 113 uac.authzInfo = &UserAuthzInfo{} 114 } 115 116 uac.authzInfo.isAdmin = isAdmin 117 } 118 119 /* 120 UserAcFromContext returns an UserAccessControl struct made available on all http requests 121 (using context.Context values) by authz and authn middlewares. 122 123 If no UserAccessControl is found on context, it will return an empty one. 124 125 its methods and attributes can be used in http.Handlers to get user info for that specific request 126 (username, groups, if it's an admin, if it can access certain resources). 127 */ 128 func UserAcFromContext(ctx context.Context) (*UserAccessControl, error) { 129 if uacValue := ctx.Value(GetContextKey()); uacValue != nil { 130 uac, ok := uacValue.(UserAccessControl) 131 if !ok { 132 return nil, errors.ErrBadType 133 } 134 135 return &uac, nil 136 } 137 138 return NewUserAccessControl(), nil 139 } 140 141 func (uac *UserAccessControl) SetGlobPatterns(action string, patterns map[string]bool) { 142 if uac.authzInfo == nil { 143 uac.authzInfo = &UserAuthzInfo{ 144 globPatterns: make(map[string]map[string]bool), 145 } 146 } 147 148 uac.authzInfo.globPatterns[action] = patterns 149 } 150 151 /* 152 Can returns whether or not the user/anonymous who made the request has 'action' permission on 'repository'. 153 */ 154 func (uac *UserAccessControl) Can(action, repository string) bool { 155 var defaultRet bool 156 if uac.isBehaviourAction(action) { 157 defaultRet = false 158 } else if uac.isMethodAction(action) { 159 defaultRet = true 160 } 161 162 if uac.IsAdmin() { 163 return defaultRet 164 } 165 166 // if glob patterns are not set then authz is not enabled, so everybody have access. 167 if !uac.areGlobPatternsSet() { 168 return defaultRet 169 } 170 171 return uac.matchesRepo(uac.authzInfo.globPatterns[action], repository) 172 } 173 174 func (uac *UserAccessControl) isBehaviourAction(action string) bool { 175 for _, behaviourAction := range uac.behaviourActions { 176 if action == behaviourAction { 177 return true 178 } 179 } 180 181 return false 182 } 183 184 func (uac *UserAccessControl) isMethodAction(action string) bool { 185 for _, methodAction := range uac.methodActions { 186 if action == methodAction { 187 return true 188 } 189 } 190 191 return false 192 } 193 194 // returns whether or not glob patterns have been set in authz.go. 195 func (uac *UserAccessControl) areGlobPatternsSet() bool { 196 notSet := uac.authzInfo == nil || uac.authzInfo.globPatterns == nil 197 198 return !notSet 199 } 200 201 /* 202 returns whether or not 'repository' can be found in the list of patterns 203 on which the user who made the request has read permission on. 204 */ 205 func (uac *UserAccessControl) matchesRepo(globPatterns map[string]bool, repository string) bool { 206 var longestMatchedPattern string 207 208 // because of the longest path matching rule, we need to check all patterns from config 209 for pattern := range globPatterns { 210 matched, err := glob.Match(pattern, repository) 211 if err == nil { 212 if matched && len(pattern) > len(longestMatchedPattern) { 213 longestMatchedPattern = pattern 214 } 215 } 216 } 217 218 allowed := globPatterns[longestMatchedPattern] 219 220 return allowed 221 } 222 223 /* 224 SaveOnRequest saves UserAccessControl on the request's context. 225 226 Later UserAcFromContext(request.Context()) can be used to obtain UserAccessControl that was saved on it. 227 */ 228 func (uac *UserAccessControl) SaveOnRequest(request *http.Request) { 229 uacContext := context.WithValue(request.Context(), GetContextKey(), *uac) 230 231 *request = *request.WithContext(uacContext) 232 } 233 234 /* 235 DeriveContext takes a context(parent) and returns a derived context(child) containing this UserAccessControl. 236 237 Later UserAcFromContext(ctx context.Context) can be used to obtain the UserAccessControl that was added on it. 238 */ 239 func (uac *UserAccessControl) DeriveContext(ctx context.Context) context.Context { 240 return context.WithValue(ctx, GetContextKey(), *uac) 241 } 242 243 func RepoIsUserAvailable(ctx context.Context, repoName string) (bool, error) { 244 uac, err := UserAcFromContext(ctx) 245 if err != nil { 246 return false, err 247 } 248 249 return uac.Can(constants.ReadPermission, repoName), nil 250 } 251 252 func CanDelete(ctx context.Context, repoName string) (bool, error) { 253 uac, err := UserAcFromContext(ctx) 254 if err != nil { 255 return false, err 256 } 257 258 return uac.Can(constants.DeletePermission, repoName), nil 259 }