Newer
Older
package auth
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"strings"
"forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/common"
"forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/tokens"
)
type key int
const (
authTokenKey string = "auth_token"
// ContextData is the user
ContextData key = 0
)
var (
AnimatorRole = common.StringValueFromEnv("ANIMATOR_ROLE", "ANIMATORS")
// AdminRole represents the role reserved for admins
AdminRole = common.StringValueFromEnv("ADMIN_ROLE", "ADMINS")
hostname = common.StringValueFromEnv("HOSTNAME", "ecolyobackoffice.127.0.0.1.nip.io")
SGEApiToken = common.StringValueFromEnv("SGE_API_TOKEN", "sgetoken")
)
// User represents a logged in user
type User struct {
ID string `json:"id,omitempty"`
Login string `json:"login"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
Roles []string `json:"memberOf"`
IsAdmin bool `json:"isAdmin,omitempty"`
Name string `json:"name,omitempty"`
Surname string `json:"surname,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
Password string `json:"password,omitempty"`
}
// TokenData represents the data held into a token
type TokenData struct {
User
URL string `json:"url,omitempty"`
ReadOnly bool `json:"readonly,omitempty"`
SharingUserLogin string `json:"sharinguserlogin,omitempty"`
XSRFToken string `json:"xsrftoken,omitempty"`
}
func AnimatorAuthMiddleware(next http.Handler) http.Handler {
return ValidateAuthMiddleware(next, []string{AdminRole, AnimatorRole}, true)
func AdminAuthMiddleware(next http.Handler) http.Handler {
return ValidateAuthMiddleware(next, []string{AdminRole}, true)
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// ValidateAuthMiddleware validates that the token is valid and that the user has the correct roles
func ValidateAuthMiddleware(next http.Handler, allowedRoles []string, checkXSRF bool) http.Handler {
roleChecker := func(w http.ResponseWriter, r *http.Request) {
user := TokenData{}
checkXSRF, err := tokens.ExtractAndValidateToken(r, authTokenKey, &user, checkXSRF)
if err != nil {
// Handle CORS preflight requests
if r.Method == "OPTIONS" {
return
}
// Default to redirect to authentication
redirectTo := hostname
_, port, perr := net.SplitHostPort(r.Host)
if perr == nil {
redirectTo += ":" + port
}
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusUnauthorized)
responseContent := fmt.Sprintf("error extracting token: %v<meta http-equiv=\"Refresh\" content=\"0; url=https://%v#login\"/>", err.Error(), redirectTo)
fmt.Fprint(w, responseContent)
return
}
// Check XSRF Token
if checkXSRF && r.Header.Get("XSRF-TOKEN") != user.XSRFToken {
http.Error(w, "XSRF protection triggered", http.StatusUnauthorized)
return
}
err = checkUserHasRole(user, allowedRoles)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
err = checkUserHasRole(user, []string{AdminRole})
if err == nil {
user.IsAdmin = true
}
// Check for url
if user.URL != "" {
requestURL := strings.Split(r.Host, ":")[0] + r.URL.EscapedPath()
if user.URL != requestURL {
http.Error(w, "token restricted to url: "+user.URL, http.StatusUnauthorized)
return
}
}
// Check for method
if user.ReadOnly && r.Method != http.MethodGet {
http.Error(w, "token is read only", http.StatusForbidden)
return
}
ctx := context.WithValue(r.Context(), ContextData, user)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(roleChecker)
}
func SGEAuthMiddleware(next http.Handler) http.Handler {
tokenChecker := func(w http.ResponseWriter, r *http.Request) {
// Check API Token
if r.Header.Get("Authorization") != "Bearer "+SGEApiToken {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(tokenChecker)
}
// HandleLogout remove the user from the cookie store
func (m Manager) HandleLogout(w http.ResponseWriter, r *http.Request) {
// Delete the auth cookie
c := http.Cookie{
Name: authTokenKey,
Domain: m.Hostname,
MaxAge: -1,
}
http.SetCookie(w, &c)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
}
// WhoAmI returns the user data
func WhoAmI() http.Handler {
whoAmI := func(w http.ResponseWriter, r *http.Request) {
user, err := GetTokenData(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(user)
}
return http.HandlerFunc(whoAmI)
}
// checkUserHasRole checks if the user has the required role
func checkUserHasRole(user TokenData, allowedRoles []string) error {
for _, allowedRole := range allowedRoles {
if allowedRole == "*" {
return nil
}
for _, userRole := range user.Roles {
if userRole != "" && (userRole == allowedRole) {
return nil
}
}
}
return fmt.Errorf("no user role among %v is in allowed roles (%v)", user.Roles, allowedRoles)
}
// GetTokenData gets an user from a request
func GetTokenData(r *http.Request) (TokenData, error) {
user, ok := r.Context().Value(ContextData).(TokenData)
if !ok {
return user, errors.New("user could not be got from context")
}
return user, nil
}