2017-04-06 18:40:57 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2017-11-01 00:58:40 +00:00
|
|
|
"fmt"
|
2017-04-06 18:40:57 +00:00
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/influxdata/chronograf"
|
|
|
|
"github.com/influxdata/chronograf/oauth2"
|
2017-11-01 13:49:02 +00:00
|
|
|
"github.com/influxdata/chronograf/organizations"
|
2017-11-03 20:06:20 +00:00
|
|
|
"github.com/influxdata/chronograf/roles"
|
2017-04-06 18:40:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// AuthorizedToken extracts the token and validates; if valid the next handler
|
|
|
|
// will be run. The principal will be sent to the next handler via the request's
|
|
|
|
// Context. It is up to the next handler to determine if the principal has access.
|
|
|
|
// On failure, will return http.StatusForbidden.
|
|
|
|
func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next http.Handler) http.HandlerFunc {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log := logger.
|
2017-10-19 18:17:40 +00:00
|
|
|
WithField("component", "token_auth").
|
2017-04-06 18:40:57 +00:00
|
|
|
WithField("remote_addr", r.RemoteAddr).
|
|
|
|
WithField("method", r.Method).
|
|
|
|
WithField("url", r.URL)
|
|
|
|
|
|
|
|
ctx := r.Context()
|
2017-04-17 16:49:45 +00:00
|
|
|
// We do not check the authorization of the principal. Those
|
2017-04-06 18:40:57 +00:00
|
|
|
// served further down the chain should do so.
|
2017-04-17 16:49:45 +00:00
|
|
|
principal, err := auth.Validate(ctx, r)
|
2017-04-06 18:40:57 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("Invalid principal")
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-04-17 16:49:45 +00:00
|
|
|
// If the principal is valid we will extend its lifespan
|
|
|
|
// into the future
|
|
|
|
principal, err = auth.Extend(ctx, w, principal)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Unable to extend principal")
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-04-06 18:40:57 +00:00
|
|
|
// Send the principal to the next handler
|
|
|
|
ctx = context.WithValue(ctx, oauth2.PrincipalKey, principal)
|
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
|
|
return
|
|
|
|
})
|
|
|
|
}
|
2017-10-18 16:35:40 +00:00
|
|
|
|
2017-10-19 16:54:06 +00:00
|
|
|
// AuthorizedUser extracts the user name and provider from context. If the
|
|
|
|
// user and provider can be found on the context, we look up the user by their
|
|
|
|
// name and provider. If the user is found, we verify that the user has at at
|
|
|
|
// least the role supplied.
|
|
|
|
func AuthorizedUser(
|
2017-10-31 20:41:17 +00:00
|
|
|
store DataStore,
|
2017-10-19 16:54:06 +00:00
|
|
|
useAuth bool,
|
|
|
|
role string,
|
|
|
|
logger chronograf.Logger,
|
|
|
|
next http.HandlerFunc,
|
|
|
|
) http.HandlerFunc {
|
2017-10-18 16:35:40 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if !useAuth {
|
2017-11-03 16:55:55 +00:00
|
|
|
ctx := r.Context()
|
|
|
|
defaultOrg, err := store.Organizations(ctx).DefaultOrganization(ctx)
|
|
|
|
if err != nil {
|
|
|
|
unknownErrorWithMessage(w, err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, organizations.ContextKey, fmt.Sprintf("%d", defaultOrg.ID))
|
2017-11-03 20:32:05 +00:00
|
|
|
ctx = context.WithValue(ctx, roles.ContextKey, roles.AdminRoleName)
|
2017-11-03 16:55:55 +00:00
|
|
|
r = r.WithContext(ctx)
|
2017-10-18 16:35:40 +00:00
|
|
|
next(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log := logger.
|
|
|
|
WithField("component", "role_auth").
|
|
|
|
WithField("remote_addr", r.RemoteAddr).
|
|
|
|
WithField("method", r.Method).
|
|
|
|
WithField("url", r.URL)
|
|
|
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
2017-10-27 20:19:43 +00:00
|
|
|
p, err := getValidPrincipal(ctx)
|
2017-10-18 16:35:40 +00:00
|
|
|
if err != nil {
|
2017-10-27 20:19:43 +00:00
|
|
|
log.Error("Failed to retrieve principal from context")
|
2017-10-18 19:45:06 +00:00
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
2017-10-18 16:35:40 +00:00
|
|
|
return
|
|
|
|
}
|
2017-10-27 20:19:43 +00:00
|
|
|
scheme, err := getScheme(ctx)
|
2017-10-18 16:35:40 +00:00
|
|
|
if err != nil {
|
2017-10-27 20:19:43 +00:00
|
|
|
log.Error("Failed to retrieve scheme from context")
|
2017-10-18 19:45:06 +00:00
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
2017-10-18 16:35:40 +00:00
|
|
|
return
|
|
|
|
}
|
2017-10-27 20:19:43 +00:00
|
|
|
|
2017-11-01 16:30:42 +00:00
|
|
|
// This is as if the user was logged into the default organization
|
2017-10-27 20:19:43 +00:00
|
|
|
if p.Organization == "" {
|
2017-11-02 20:47:45 +00:00
|
|
|
defaultOrg, err := store.Organizations(ctx).DefaultOrganization(ctx)
|
|
|
|
if err != nil {
|
|
|
|
unknownErrorWithMessage(w, err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.Organization = fmt.Sprintf("%d", defaultOrg.ID)
|
2017-10-27 20:19:43 +00:00
|
|
|
}
|
2017-11-01 00:58:40 +00:00
|
|
|
|
2017-10-27 20:19:43 +00:00
|
|
|
// validate that the organization exists
|
2017-11-01 16:34:00 +00:00
|
|
|
orgID, err := parseOrganizationID(p.Organization)
|
2017-10-19 18:17:40 +00:00
|
|
|
if err != nil {
|
2017-10-27 20:19:43 +00:00
|
|
|
log.Error("Failed to validate organization on context")
|
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
|
|
|
return
|
|
|
|
}
|
2017-10-31 20:41:17 +00:00
|
|
|
_, err = store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID})
|
2017-10-27 20:19:43 +00:00
|
|
|
if err != nil {
|
2017-11-01 00:58:40 +00:00
|
|
|
log.Error(fmt.Sprintf("Failed to retrieve organization %d from organizations store", orgID))
|
2017-10-19 18:17:40 +00:00
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
|
|
|
return
|
|
|
|
}
|
2017-10-18 16:35:40 +00:00
|
|
|
|
2017-11-01 13:49:02 +00:00
|
|
|
ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization)
|
|
|
|
serverCtx := context.WithValue(ctx, SuperAdminKey, true)
|
2017-11-03 20:06:20 +00:00
|
|
|
// the DataStore expects that the roles context key be set for future calls
|
2017-11-03 20:32:05 +00:00
|
|
|
serverCtx = context.WithValue(serverCtx, roles.ContextKey, roles.AdminRoleName)
|
2017-10-31 22:27:24 +00:00
|
|
|
// TODO: seems silly to look up a user twice
|
|
|
|
u, err := store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{
|
|
|
|
Name: &p.Subject,
|
|
|
|
Provider: &p.Issuer,
|
|
|
|
Scheme: &scheme,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Failed to retrieve user")
|
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if u.SuperAdmin {
|
|
|
|
// This context is where superadmin gets set for all things
|
|
|
|
r = r.WithContext(serverCtx)
|
|
|
|
next(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err = store.Users(ctx).Get(ctx, chronograf.UserQuery{
|
2017-10-27 20:19:43 +00:00
|
|
|
Name: &p.Subject,
|
|
|
|
Provider: &p.Issuer,
|
2017-10-19 18:17:40 +00:00
|
|
|
Scheme: &scheme,
|
|
|
|
})
|
2017-10-18 16:35:40 +00:00
|
|
|
if err != nil {
|
2017-10-19 16:54:06 +00:00
|
|
|
log.Error("Failed to retrieve user")
|
2017-10-18 19:45:06 +00:00
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
2017-10-18 16:35:40 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-19 16:54:06 +00:00
|
|
|
if hasAuthorizedRole(u, role) {
|
2017-11-03 20:06:20 +00:00
|
|
|
// use the first role, since there should only ever be one
|
|
|
|
// for any particular organization and hasAuthorizedRole
|
|
|
|
// should ensure that at least one role for the org exists
|
|
|
|
ctx = context.WithValue(ctx, roles.ContextKey, u.Roles[0].Name)
|
2017-11-03 13:13:03 +00:00
|
|
|
r = r.WithContext(ctx)
|
2017-10-18 16:35:40 +00:00
|
|
|
next(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-18 19:45:06 +00:00
|
|
|
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
|
2017-10-18 16:35:40 +00:00
|
|
|
return
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-10-19 16:54:06 +00:00
|
|
|
func hasAuthorizedRole(u *chronograf.User, role string) bool {
|
2017-10-18 16:35:40 +00:00
|
|
|
if u == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch role {
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.ViewerRoleName:
|
2017-10-18 16:35:40 +00:00
|
|
|
for _, r := range u.Roles {
|
|
|
|
switch r.Name {
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName:
|
2017-10-18 16:35:40 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.EditorRoleName:
|
2017-10-18 16:35:40 +00:00
|
|
|
for _, r := range u.Roles {
|
|
|
|
switch r.Name {
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.EditorRoleName, roles.AdminRoleName:
|
2017-10-18 16:35:40 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.AdminRoleName:
|
2017-10-18 16:35:40 +00:00
|
|
|
for _, r := range u.Roles {
|
|
|
|
switch r.Name {
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.AdminRoleName:
|
2017-10-18 16:35:40 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2017-11-03 20:32:05 +00:00
|
|
|
case roles.SuperAdminRoleName:
|
2017-11-01 00:58:40 +00:00
|
|
|
// SuperAdmins should have been authorized before this.
|
|
|
|
// This is only meant to restrict access for non-superadmins.
|
|
|
|
return false
|
2017-10-18 16:35:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|