2023-06-22 14:29:45 +03:00
package common
import (
"net/http"
2023-07-10 19:40:14 +03:00
"net/url"
2023-07-07 19:27:10 +03:00
"strconv"
2023-06-22 14:29:45 +03:00
"strings"
"time"
"github.com/gorilla/mux"
jsoniter "github.com/json-iterator/go"
2024-02-01 06:34:07 +02:00
"zotregistry.dev/zot/pkg/api/config"
"zotregistry.dev/zot/pkg/api/constants"
apiErr "zotregistry.dev/zot/pkg/api/errors"
reqCtx "zotregistry.dev/zot/pkg/requestcontext"
2023-06-22 14:29:45 +03:00
)
func AllowedMethods ( methods ... string ) [ ] string {
return append ( methods , http . MethodOptions )
}
func AddExtensionSecurityHeaders ( ) mux . MiddlewareFunc { //nolint:varnamelen
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( resp http . ResponseWriter , req * http . Request ) {
resp . Header ( ) . Set ( "X-Content-Type-Options" , "nosniff" )
next . ServeHTTP ( resp , req )
} )
}
}
2023-08-02 21:58:34 +03:00
func ACHeadersMiddleware ( config * config . Config , allowedMethods ... string ) mux . MiddlewareFunc {
2023-07-19 19:27:04 +03:00
allowedMethodsValue := strings . Join ( allowedMethods , "," )
2023-06-22 14:29:45 +03:00
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( resp http . ResponseWriter , req * http . Request ) {
2023-07-19 19:27:04 +03:00
resp . Header ( ) . Set ( "Access-Control-Allow-Methods" , allowedMethodsValue )
2023-07-07 19:27:10 +03:00
resp . Header ( ) . Set ( "Access-Control-Allow-Headers" , "Authorization,content-type," + constants . SessionClientHeaderName )
2023-07-19 19:27:04 +03:00
if config . IsBasicAuthnEnabled ( ) {
resp . Header ( ) . Set ( "Access-Control-Allow-Credentials" , "true" )
}
2023-06-22 14:29:45 +03:00
if req . Method == http . MethodOptions {
return
}
next . ServeHTTP ( resp , req )
} )
}
}
2023-08-02 21:58:34 +03:00
func CORSHeadersMiddleware ( allowOrigin string ) mux . MiddlewareFunc {
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( response http . ResponseWriter , request * http . Request ) {
AddCORSHeaders ( allowOrigin , response )
next . ServeHTTP ( response , request )
} )
}
}
func AddCORSHeaders ( allowOrigin string , response http . ResponseWriter ) {
if allowOrigin == "" {
response . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
} else {
response . Header ( ) . Set ( "Access-Control-Allow-Origin" , allowOrigin )
}
}
// AuthzOnlyAdminsMiddleware permits only admin user access if auth is enabled.
func AuthzOnlyAdminsMiddleware ( conf * config . Config ) mux . MiddlewareFunc {
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( response http . ResponseWriter , request * http . Request ) {
if ! conf . IsBasicAuthnEnabled ( ) {
next . ServeHTTP ( response , request )
return
}
2023-09-01 21:13:53 +03:00
// get userAccessControl built in previous authn/authz middlewares
userAc , err := reqCtx . UserAcFromContext ( request . Context ( ) )
2023-08-02 21:58:34 +03:00
if err != nil { // should not happen as this has been previously checked for errors
2023-09-01 21:13:53 +03:00
AuthzFail ( response , request , userAc . GetUsername ( ) , conf . HTTP . Realm , conf . HTTP . Auth . FailDelay )
2023-08-02 21:58:34 +03:00
return
}
// reject non-admin access if authentication is enabled
2023-09-01 21:13:53 +03:00
if userAc != nil && ! userAc . IsAdmin ( ) {
AuthzFail ( response , request , userAc . GetUsername ( ) , conf . HTTP . Realm , conf . HTTP . Auth . FailDelay )
2023-08-02 21:58:34 +03:00
return
}
next . ServeHTTP ( response , request )
} )
}
}
2023-09-01 21:13:53 +03:00
func AuthzFail ( w http . ResponseWriter , r * http . Request , identity , realm string , delay int ) {
2023-06-22 14:29:45 +03:00
time . Sleep ( time . Duration ( delay ) * time . Second )
2023-07-07 19:27:10 +03:00
// don't send auth headers if request is coming from UI
if r . Header . Get ( constants . SessionClientHeaderName ) != constants . SessionClientHeaderValue {
if realm == "" {
realm = "Authorization Required"
}
realm = "Basic realm=" + strconv . Quote ( realm )
w . Header ( ) . Set ( "WWW-Authenticate" , realm )
}
2023-06-22 14:29:45 +03:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2023-09-01 21:13:53 +03:00
if identity == "" {
WriteJSON ( w , http . StatusUnauthorized , apiErr . NewErrorList ( apiErr . NewError ( apiErr . UNAUTHORIZED ) ) )
} else {
WriteJSON ( w , http . StatusForbidden , apiErr . NewErrorList ( apiErr . NewError ( apiErr . DENIED ) ) )
}
2023-06-22 14:29:45 +03:00
}
func WriteJSON ( response http . ResponseWriter , status int , data interface { } ) {
json := jsoniter . ConfigCompatibleWithStandardLibrary
body , err := json . Marshal ( data )
if err != nil {
panic ( err )
}
WriteData ( response , status , constants . DefaultMediaType , body )
}
func WriteData ( w http . ResponseWriter , status int , mediaType string , data [ ] byte ) {
w . Header ( ) . Set ( "Content-Type" , mediaType )
w . WriteHeader ( status )
_ , _ = w . Write ( data )
}
2023-07-07 19:27:10 +03:00
2023-07-10 19:40:14 +03:00
func QueryHasParams ( values url . Values , params [ ] string ) bool {
for _ , param := range params {
if ! values . Has ( param ) {
return false
}
}
return true
}