2017-03-10 19:24:48 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2017-10-26 22:01:20 +00:00
|
|
|
"encoding/json"
|
2017-03-10 19:24:48 +00:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
|
|
|
"github.com/influxdata/chronograf"
|
|
|
|
"github.com/influxdata/chronograf/oauth2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type meLinks struct {
|
|
|
|
Self string `json:"self"` // Self link mapping to this resource
|
|
|
|
}
|
|
|
|
|
|
|
|
type meResponse struct {
|
|
|
|
*chronograf.User
|
|
|
|
Links meLinks `json:"links"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// If new user response is nil, return an empty meResponse because it
|
|
|
|
// indicates authentication is not needed
|
|
|
|
func newMeResponse(usr *chronograf.User) meResponse {
|
|
|
|
base := "/chronograf/v1/users"
|
|
|
|
name := "me"
|
|
|
|
if usr != nil {
|
2017-04-07 20:32:35 +00:00
|
|
|
name = PathEscape(usr.Name)
|
2017-03-10 19:24:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return meResponse{
|
|
|
|
User: usr,
|
|
|
|
Links: meLinks{
|
|
|
|
Self: fmt.Sprintf("%s/%s", base, name),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-16 23:55:09 +00:00
|
|
|
func getUsername(ctx context.Context) (string, error) {
|
2017-03-10 19:24:48 +00:00
|
|
|
principal, err := getPrincipal(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if principal.Subject == "" {
|
|
|
|
return "", fmt.Errorf("Token not found")
|
|
|
|
}
|
|
|
|
return principal.Subject, nil
|
|
|
|
}
|
|
|
|
|
2017-10-17 00:30:23 +00:00
|
|
|
func getProvider(ctx context.Context) (string, error) {
|
|
|
|
principal, err := getPrincipal(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if principal.Issuer == "" {
|
|
|
|
return "", fmt.Errorf("Token not found")
|
|
|
|
}
|
|
|
|
return principal.Issuer, nil
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:17:40 +00:00
|
|
|
// TODO: This Scheme value is hard-coded temporarily since we only currently
|
|
|
|
// support OAuth2. This hard-coding should be removed whenever we add
|
|
|
|
// support for other authentication schemes.
|
|
|
|
func getScheme(ctx context.Context) (string, error) {
|
2017-10-24 23:17:59 +00:00
|
|
|
return "oauth2", nil
|
2017-10-19 18:17:40 +00:00
|
|
|
}
|
|
|
|
|
2017-03-10 19:24:48 +00:00
|
|
|
func getPrincipal(ctx context.Context) (oauth2.Principal, error) {
|
|
|
|
principal, ok := ctx.Value(oauth2.PrincipalKey).(oauth2.Principal)
|
|
|
|
if !ok {
|
|
|
|
return oauth2.Principal{}, fmt.Errorf("Token not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
return principal, nil
|
|
|
|
}
|
|
|
|
|
2017-10-26 22:01:20 +00:00
|
|
|
// This is the user's current chronograf organization and is not related to any
|
|
|
|
// concept of a OAuth organization.
|
|
|
|
func getOrganization(ctx context.Context) (string, error) {
|
|
|
|
principal, err := getPrincipal(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return principal.Organization, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type meOrganizationRequest struct {
|
2017-10-26 23:02:29 +00:00
|
|
|
OrganizationID string `json:"currentOrganization"`
|
2017-10-26 22:01:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) MeOrganization(auth oauth2.Authenticator) func(http.ResponseWriter, *http.Request) {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
principal, err := auth.Validate(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
s.Logger.Error("Invalid principal")
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var req meOrganizationRequest
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
|
invalidJSON(w, s.Logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: add logic for validating that the org exists and user belongs to that org
|
2017-10-26 23:02:29 +00:00
|
|
|
// TODO: change to principal.CurrentOrganization
|
2017-10-26 22:01:20 +00:00
|
|
|
principal.Organization = req.OrganizationID
|
|
|
|
|
|
|
|
if err := auth.Authorize(ctx, w, principal); err != nil {
|
|
|
|
Error(w, http.StatusInternalServerError, err.Error(), s.Logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = context.WithValue(ctx, oauth2.PrincipalKey, principal)
|
|
|
|
|
|
|
|
s.Me(w, r.WithContext(ctx))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-16 23:55:09 +00:00
|
|
|
// Me does a findOrCreate based on the username in the context
|
2017-10-10 22:27:58 +00:00
|
|
|
func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
|
2017-03-10 19:24:48 +00:00
|
|
|
ctx := r.Context()
|
2017-10-10 22:27:58 +00:00
|
|
|
if !s.UseAuth {
|
2017-03-10 19:24:48 +00:00
|
|
|
// If there's no authentication, return an empty user
|
|
|
|
res := newMeResponse(nil)
|
2017-10-10 22:27:58 +00:00
|
|
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
2017-03-10 19:24:48 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-16 23:55:09 +00:00
|
|
|
username, err := getUsername(ctx)
|
2017-03-10 19:24:48 +00:00
|
|
|
if err != nil {
|
2017-10-10 22:27:58 +00:00
|
|
|
invalidData(w, err, s.Logger)
|
2017-03-10 19:24:48 +00:00
|
|
|
return
|
|
|
|
}
|
2017-10-17 00:30:23 +00:00
|
|
|
provider, err := getProvider(ctx)
|
|
|
|
if err != nil {
|
|
|
|
invalidData(w, err, s.Logger)
|
|
|
|
return
|
|
|
|
}
|
2017-10-19 18:17:40 +00:00
|
|
|
scheme, err := getScheme(ctx)
|
|
|
|
if err != nil {
|
|
|
|
invalidData(w, err, s.Logger)
|
|
|
|
return
|
|
|
|
}
|
2017-10-26 22:01:20 +00:00
|
|
|
organization, err := getOrganization(ctx)
|
|
|
|
if err != nil {
|
|
|
|
invalidData(w, err, s.Logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: add real implementation
|
|
|
|
ctx = context.WithValue(ctx, "organizationID", organization)
|
2017-10-17 00:30:23 +00:00
|
|
|
|
2017-10-19 18:17:40 +00:00
|
|
|
usr, err := s.UsersStore.Get(ctx, chronograf.UserQuery{
|
|
|
|
Name: &username,
|
|
|
|
Provider: &provider,
|
|
|
|
Scheme: &scheme,
|
|
|
|
})
|
2017-10-18 18:45:33 +00:00
|
|
|
if err != nil && err != chronograf.ErrUserNotFound {
|
2017-10-18 16:34:23 +00:00
|
|
|
unknownErrorWithMessage(w, err, s.Logger)
|
2017-10-17 00:30:23 +00:00
|
|
|
return
|
|
|
|
}
|
2017-03-10 19:24:48 +00:00
|
|
|
|
2017-10-17 00:30:23 +00:00
|
|
|
if usr != nil {
|
2017-10-26 22:01:20 +00:00
|
|
|
usr.CurrentOrganization = organization
|
2017-03-10 19:24:48 +00:00
|
|
|
res := newMeResponse(usr)
|
2017-10-10 22:27:58 +00:00
|
|
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
2017-03-10 19:24:48 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because we didnt find a user, making a new one
|
|
|
|
user := &chronograf.User{
|
2017-10-19 18:17:40 +00:00
|
|
|
Name: username,
|
|
|
|
Provider: provider,
|
|
|
|
// TODO: This Scheme value is hard-coded temporarily since we only currently
|
|
|
|
// support OAuth2. This hard-coding should be removed whenever we add
|
|
|
|
// support for other authentication schemes.
|
2017-10-24 23:17:59 +00:00
|
|
|
Scheme: "oauth2",
|
2017-03-10 19:24:48 +00:00
|
|
|
}
|
|
|
|
|
2017-10-10 22:27:58 +00:00
|
|
|
newUser, err := s.UsersStore.Add(ctx, user)
|
2017-03-10 19:24:48 +00:00
|
|
|
if err != nil {
|
|
|
|
msg := fmt.Errorf("error storing user %s: %v", user.Name, err)
|
2017-10-10 22:27:58 +00:00
|
|
|
unknownErrorWithMessage(w, msg, s.Logger)
|
2017-03-10 19:24:48 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-26 22:01:20 +00:00
|
|
|
newUser.CurrentOrganization = organization
|
2017-03-10 19:24:48 +00:00
|
|
|
res := newMeResponse(newUser)
|
2017-10-10 22:27:58 +00:00
|
|
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
2017-03-10 19:24:48 +00:00
|
|
|
}
|