influxdb/chronograf/server/mapping.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
}