265 lines
6.1 KiB
Go
265 lines
6.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/bouk/httprouter"
|
|
"github.com/influxdata/influxdb/v2/chronograf"
|
|
"github.com/influxdata/influxdb/v2/chronograf/oauth2"
|
|
)
|
|
|
|
func (s *Service) mapPrincipalToSuperAdmin(p oauth2.Principal) bool {
|
|
if p.Issuer != "auth0" {
|
|
return false
|
|
}
|
|
|
|
groups := strings.Split(p.Group, ",")
|
|
superAdmin := false
|
|
for _, group := range groups {
|
|
if group != "" && group == s.SuperAdminProviderGroups.auth0 {
|
|
superAdmin = true
|
|
break
|
|
}
|
|
}
|
|
return superAdmin
|
|
}
|
|
|
|
func (s *Service) mapPrincipalToRoles(ctx context.Context, p oauth2.Principal) ([]chronograf.Role, error) {
|
|
mappings, err := s.Store.Mappings(ctx).All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
roles := []chronograf.Role{}
|
|
MappingsLoop:
|
|
for _, mapping := range mappings {
|
|
if applyMapping(mapping, p) {
|
|
org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &mapping.Organization})
|
|
if err != nil {
|
|
continue MappingsLoop
|
|
}
|
|
|
|
for _, role := range roles {
|
|
if role.Organization == org.ID {
|
|
continue MappingsLoop
|
|
}
|
|
}
|
|
roles = append(roles, chronograf.Role{Organization: org.ID, Name: org.DefaultRole})
|
|
}
|
|
}
|
|
|
|
return roles, nil
|
|
}
|
|
|
|
func applyMapping(m chronograf.Mapping, p oauth2.Principal) bool {
|
|
switch m.Provider {
|
|
case chronograf.MappingWildcard, p.Issuer:
|
|
default:
|
|
return false
|
|
}
|
|
|
|
switch m.Scheme {
|
|
case chronograf.MappingWildcard, "oauth2":
|
|
default:
|
|
return false
|
|
}
|
|
|
|
if m.ProviderOrganization == chronograf.MappingWildcard {
|
|
return true
|
|
}
|
|
|
|
groups := strings.Split(p.Group, ",")
|
|
|
|
return matchGroup(m.ProviderOrganization, groups)
|
|
}
|
|
|
|
func matchGroup(match string, groups []string) bool {
|
|
for _, group := range groups {
|
|
if match == group {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type mappingsRequest chronograf.Mapping
|
|
|
|
// Valid determines if a mapping request is valid
|
|
func (m *mappingsRequest) Valid() error {
|
|
if m.Provider == "" {
|
|
return fmt.Errorf("mapping must specify provider")
|
|
}
|
|
if m.Scheme == "" {
|
|
return fmt.Errorf("mapping must specify scheme")
|
|
}
|
|
if m.ProviderOrganization == "" {
|
|
return fmt.Errorf("mapping must specify group")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type mappingResponse struct {
|
|
Links selfLinks `json:"links"`
|
|
chronograf.Mapping
|
|
}
|
|
|
|
func newMappingResponse(m chronograf.Mapping) *mappingResponse {
|
|
|
|
return &mappingResponse{
|
|
Links: selfLinks{
|
|
Self: fmt.Sprintf("/chronograf/v1/mappings/%s", m.ID),
|
|
},
|
|
Mapping: m,
|
|
}
|
|
}
|
|
|
|
type mappingsResponse struct {
|
|
Links selfLinks `json:"links"`
|
|
Mappings []*mappingResponse `json:"mappings"`
|
|
}
|
|
|
|
func newMappingsResponse(ms []chronograf.Mapping) *mappingsResponse {
|
|
mappings := []*mappingResponse{}
|
|
for _, m := range ms {
|
|
mappings = append(mappings, newMappingResponse(m))
|
|
}
|
|
return &mappingsResponse{
|
|
Links: selfLinks{
|
|
Self: "/chronograf/v1/mappings",
|
|
},
|
|
Mappings: mappings,
|
|
}
|
|
}
|
|
|
|
// Mappings retrieves all mappings
|
|
func (s *Service) Mappings(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
mappings, err := s.Store.Mappings(ctx).All(ctx)
|
|
if err != nil {
|
|
Error(w, http.StatusInternalServerError, "failed to retrieve mappings from database", s.Logger)
|
|
return
|
|
}
|
|
|
|
res := newMappingsResponse(mappings)
|
|
|
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
|
}
|
|
|
|
// NewMapping adds a new mapping
|
|
func (s *Service) NewMapping(w http.ResponseWriter, r *http.Request) {
|
|
var req mappingsRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
invalidJSON(w, s.Logger)
|
|
return
|
|
}
|
|
|
|
if err := req.Valid(); err != nil {
|
|
invalidData(w, err, s.Logger)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
|
|
// validate that the organization exists
|
|
if !s.organizationExists(ctx, req.Organization) {
|
|
invalidData(w, fmt.Errorf("organization does not exist"), s.Logger)
|
|
return
|
|
}
|
|
|
|
mapping := &chronograf.Mapping{
|
|
Organization: req.Organization,
|
|
Scheme: req.Scheme,
|
|
Provider: req.Provider,
|
|
ProviderOrganization: req.ProviderOrganization,
|
|
}
|
|
|
|
m, err := s.Store.Mappings(ctx).Add(ctx, mapping)
|
|
if err != nil {
|
|
Error(w, http.StatusInternalServerError, "failed to add mapping to database", s.Logger)
|
|
return
|
|
}
|
|
|
|
cu := newMappingResponse(*m)
|
|
location(w, cu.Links.Self)
|
|
encodeJSON(w, http.StatusCreated, cu, s.Logger)
|
|
}
|
|
|
|
// UpdateMapping updates a mapping
|
|
func (s *Service) UpdateMapping(w http.ResponseWriter, r *http.Request) {
|
|
var req mappingsRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
invalidJSON(w, s.Logger)
|
|
return
|
|
}
|
|
|
|
if err := req.Valid(); err != nil {
|
|
invalidData(w, err, s.Logger)
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
|
|
// validate that the organization exists
|
|
if !s.organizationExists(ctx, req.Organization) {
|
|
invalidData(w, fmt.Errorf("organization does not exist"), s.Logger)
|
|
return
|
|
}
|
|
|
|
mapping := &chronograf.Mapping{
|
|
ID: req.ID,
|
|
Organization: req.Organization,
|
|
Scheme: req.Scheme,
|
|
Provider: req.Provider,
|
|
ProviderOrganization: req.ProviderOrganization,
|
|
}
|
|
|
|
err := s.Store.Mappings(ctx).Update(ctx, mapping)
|
|
if err != nil {
|
|
Error(w, http.StatusInternalServerError, "failed to update mapping in database", s.Logger)
|
|
return
|
|
}
|
|
|
|
cu := newMappingResponse(*mapping)
|
|
location(w, cu.Links.Self)
|
|
encodeJSON(w, http.StatusOK, cu, s.Logger)
|
|
}
|
|
|
|
// RemoveMapping removes a mapping
|
|
func (s *Service) RemoveMapping(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
id := httprouter.GetParamFromContext(ctx, "id")
|
|
|
|
m, err := s.Store.Mappings(ctx).Get(ctx, id)
|
|
if err == chronograf.ErrMappingNotFound {
|
|
Error(w, http.StatusNotFound, err.Error(), s.Logger)
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
Error(w, http.StatusInternalServerError, "failed to retrieve mapping from database", s.Logger)
|
|
return
|
|
}
|
|
|
|
if err := s.Store.Mappings(ctx).Delete(ctx, m); err != nil {
|
|
Error(w, http.StatusInternalServerError, "failed to remove mapping from database", s.Logger)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (s *Service) organizationExists(ctx context.Context, orgID string) bool {
|
|
if _, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID}); err != nil {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|