zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/common/http_server.go (about) 1 package common 2 3 import ( 4 "net/http" 5 "net/url" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/gorilla/mux" 11 jsoniter "github.com/json-iterator/go" 12 13 "zotregistry.io/zot/pkg/api/config" 14 "zotregistry.io/zot/pkg/api/constants" 15 apiErr "zotregistry.io/zot/pkg/api/errors" 16 reqCtx "zotregistry.io/zot/pkg/requestcontext" 17 ) 18 19 func AllowedMethods(methods ...string) []string { 20 return append(methods, http.MethodOptions) 21 } 22 23 func AddExtensionSecurityHeaders() mux.MiddlewareFunc { //nolint:varnamelen 24 return func(next http.Handler) http.Handler { 25 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 26 resp.Header().Set("X-Content-Type-Options", "nosniff") 27 28 next.ServeHTTP(resp, req) 29 }) 30 } 31 } 32 33 func ACHeadersMiddleware(config *config.Config, allowedMethods ...string) mux.MiddlewareFunc { 34 allowedMethodsValue := strings.Join(allowedMethods, ",") 35 36 return func(next http.Handler) http.Handler { 37 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 38 resp.Header().Set("Access-Control-Allow-Methods", allowedMethodsValue) 39 resp.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type,"+constants.SessionClientHeaderName) 40 41 if config.IsBasicAuthnEnabled() { 42 resp.Header().Set("Access-Control-Allow-Credentials", "true") 43 } 44 45 if req.Method == http.MethodOptions { 46 return 47 } 48 49 next.ServeHTTP(resp, req) 50 }) 51 } 52 } 53 54 func CORSHeadersMiddleware(allowOrigin string) mux.MiddlewareFunc { 55 return func(next http.Handler) http.Handler { 56 return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { 57 AddCORSHeaders(allowOrigin, response) 58 59 next.ServeHTTP(response, request) 60 }) 61 } 62 } 63 64 func AddCORSHeaders(allowOrigin string, response http.ResponseWriter) { 65 if allowOrigin == "" { 66 response.Header().Set("Access-Control-Allow-Origin", "*") 67 } else { 68 response.Header().Set("Access-Control-Allow-Origin", allowOrigin) 69 } 70 } 71 72 // AuthzOnlyAdminsMiddleware permits only admin user access if auth is enabled. 73 func AuthzOnlyAdminsMiddleware(conf *config.Config) mux.MiddlewareFunc { 74 return func(next http.Handler) http.Handler { 75 return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { 76 if !conf.IsBasicAuthnEnabled() { 77 next.ServeHTTP(response, request) 78 79 return 80 } 81 82 // get userAccessControl built in previous authn/authz middlewares 83 userAc, err := reqCtx.UserAcFromContext(request.Context()) 84 if err != nil { // should not happen as this has been previously checked for errors 85 AuthzFail(response, request, userAc.GetUsername(), conf.HTTP.Realm, conf.HTTP.Auth.FailDelay) 86 87 return 88 } 89 90 // reject non-admin access if authentication is enabled 91 if userAc != nil && !userAc.IsAdmin() { 92 AuthzFail(response, request, userAc.GetUsername(), conf.HTTP.Realm, conf.HTTP.Auth.FailDelay) 93 94 return 95 } 96 97 next.ServeHTTP(response, request) 98 }) 99 } 100 } 101 102 func AuthzFail(w http.ResponseWriter, r *http.Request, identity, realm string, delay int) { 103 time.Sleep(time.Duration(delay) * time.Second) 104 105 // don't send auth headers if request is coming from UI 106 if r.Header.Get(constants.SessionClientHeaderName) != constants.SessionClientHeaderValue { 107 if realm == "" { 108 realm = "Authorization Required" 109 } 110 111 realm = "Basic realm=" + strconv.Quote(realm) 112 113 w.Header().Set("WWW-Authenticate", realm) 114 } 115 116 w.Header().Set("Content-Type", "application/json") 117 118 if identity == "" { 119 WriteJSON(w, http.StatusUnauthorized, apiErr.NewErrorList(apiErr.NewError(apiErr.UNAUTHORIZED))) 120 } else { 121 WriteJSON(w, http.StatusForbidden, apiErr.NewErrorList(apiErr.NewError(apiErr.DENIED))) 122 } 123 } 124 125 func WriteJSON(response http.ResponseWriter, status int, data interface{}) { 126 json := jsoniter.ConfigCompatibleWithStandardLibrary 127 128 body, err := json.Marshal(data) 129 if err != nil { 130 panic(err) 131 } 132 133 WriteData(response, status, constants.DefaultMediaType, body) 134 } 135 136 func WriteData(w http.ResponseWriter, status int, mediaType string, data []byte) { 137 w.Header().Set("Content-Type", mediaType) 138 w.WriteHeader(status) 139 _, _ = w.Write(data) 140 } 141 142 func QueryHasParams(values url.Values, params []string) bool { 143 for _, param := range params { 144 if !values.Has(param) { 145 return false 146 } 147 } 148 149 return true 150 }