// Copyright 2019 Drone IO, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package acl import ( "net/http" "github.com/drone/drone/core" "github.com/drone/drone/handler/api/errors" "github.com/drone/drone/handler/api/render" "github.com/drone/drone/handler/api/request" "github.com/drone/drone/logger" "github.com/go-chi/chi" "github.com/sirupsen/logrus" ) // CheckReadAccess returns an http.Handler middleware that authorizes only // authenticated users with read repository access to proceed to the next // handler in the chain. func CheckReadAccess() func(http.Handler) http.Handler { return CheckAccess(true, false, false) } // CheckWriteAccess returns an http.Handler middleware that authorizes only // authenticated users with write repository access to proceed to the next // handler in the chain. func CheckWriteAccess() func(http.Handler) http.Handler { return CheckAccess(true, true, false) } // CheckAdminAccess returns an http.Handler middleware that authorizes only // authenticated users with admin repository access to proceed to the next // handler in the chain. func CheckAdminAccess() func(http.Handler) http.Handler { return CheckAccess(true, true, true) } // CheckAccess returns an http.Handler middleware that authorizes only // authenticated users with the required read, write or admin access // permissions to the requested repository resource. func CheckAccess(read, write, admin bool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() owner = chi.URLParam(r, "owner") name = chi.URLParam(r, "name") ) log := logger.FromRequest(r). WithField("namespace", owner). WithField("name", name) user, ok := request.UserFrom(ctx) switch { case ok == false && write == true: render.Unauthorized(w, errors.ErrUnauthorized) log.Debugln("api: authentication required for write access") return case ok == false && admin == true: render.Unauthorized(w, errors.ErrUnauthorized) log.Debugln("api: authentication required for admin access") return case ok == true && user.Admin == true: log.Debugln("api: root access granted") next.ServeHTTP(w, r) return } repo, noRepo := request.RepoFrom(ctx) if !noRepo { // this should never happen. the repository // should always be injected into the context // by an upstream handler in the chain. log.Errorln("api: null repository in context") render.NotFound(w, errors.ErrNotFound) return } log = log.WithField("visibility", repo.Visibility) switch { case admin == true: // continue case write == true: // continue case repo.Visibility == core.VisibilityPublic: log.Debugln("api: read access granted") next.ServeHTTP(w, r) return case ok == false: render.Unauthorized(w, errors.ErrUnauthorized) log.Debugln("api: authentication required") return case ok == true && repo.Visibility == core.VisibilityInternal: log.Debugln("api: read access granted") next.ServeHTTP(w, r) return } perm, ok := request.PermFrom(ctx) if !ok { render.NotFound(w, errors.ErrNotFound) log.Debugln("api: repository permissions not found") return } log = log.WithFields( logrus.Fields{ "read": perm.Read, "write": perm.Write, "admin": perm.Admin, }, ) switch { case read == true && perm.Read == false: render.NotFound(w, errors.ErrNotFound) log.Debugln("api: read access required") case write == true && perm.Write == false: render.NotFound(w, errors.ErrNotFound) log.Debugln("api: write access required") case admin == true && perm.Admin == false: render.NotFound(w, errors.ErrNotFound) log.Debugln("api: admin access required") default: log.Debug("api: access granted") next.ServeHTTP(w, r.WithContext( request.WithPerm(ctx, perm), )) } }) } }