harness-drone/handler/api/acl/check.go

187 lines
5.7 KiB
Go

// 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 (
"github.com/dchest/authcookie"
"net/http"
"os"
"strings"
"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)
}
func CheckInternalAccess() func(http.Handler) http.Handler {
return checkIAccess()
}
// CheckIAccess returns an http.Handler middleware that authorizes only
// allows plugins to communicate with the server
func checkIAccess() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
owner = chi.URLParam(r, "owner")
name = chi.URLParam(r, "name")
)
log := logger.FromRequest(r).
WithField("namespace", owner).
WithField("name", name)
secret := []byte(os.Getenv("DRONE_INTERNAL_AUTH_SECRET"))
login := authcookie.Login(extractToken(r), secret)
if login != "" {
log.Debug("api: access granted")
next.ServeHTTP(w, r)
}
render.Unauthorized(w, errors.ErrUnauthorized)
log.Debugln("api: authentication required for access")
return
})
}
}
// 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 user.Active == false:
render.Forbidden(w, errors.ErrForbidden)
log.Debugln("api: active account required")
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),
))
}
})
}
}
func extractToken(r *http.Request) string {
bearer := r.Header.Get("Authorization")
if bearer == "" {
bearer = r.FormValue("access_token")
}
return strings.TrimPrefix(bearer, "Bearer ")
}