Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package auth
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tokens"
"golang.org/x/oauth2"
)
const (
oAuth2StateKey string = "oauth2_state"
)
// Manager exposes the handlers for OAuth2 endpoints
type Manager struct {
Config *oauth2.Config
Hostname string
UserInfoURL string
}
// NewManager returns a new Manager according to environment variables
func NewManager() Manager {
return Manager{Config: &oauth2.Config{
RedirectURL: os.Getenv("REDIRECT_URL"),
ClientID: os.Getenv("CLIENT_ID"),
ClientSecret: os.Getenv("CLIENT_SECRET"),
Scopes: []string{"login", "memberOf", "displayName", "email"},
Endpoint: oauth2.Endpoint{
AuthURL: os.Getenv("AUTH_URL"),
TokenURL: os.Getenv("TOKEN_URL"),
},
},
Hostname: common.StringValueFromEnv("HOSTNAME", "ecolyobackoffice.127.0.0.1.nip.io"),
UserInfoURL: os.Getenv("USERINFO_URL"),
}
}
// HandleOAuth2Login handles the OAuth2 login
func (m Manager) HandleOAuth2Login(w http.ResponseWriter, r *http.Request) {
// Generate state and store it in cookie
oauthStateString, err := common.GenerateRandomString(16)
if err != nil {
log.Fatalf("Error generating OAuth2 strate string :%v\n", err)
}
tokens.CreateCookie(oauthStateString, m.Hostname, oAuth2StateKey, 60*time.Second, w)
54
55
56
57
58
59
60
61
62
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
119
120
121
122
123
url := m.Config.AuthCodeURL(oauthStateString)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
// HandleOAuth2Callback handles the OAuth2 Callback and get user info
func (m Manager) HandleOAuth2Callback() http.Handler {
oauth2Handler := func(w http.ResponseWriter, r *http.Request) {
// Recover state from tokens
var oauthState string
_, err := tokens.ExtractAndValidateToken(r, oAuth2StateKey, &oauthState, false)
if err != nil {
fmt.Println("Code exchange failed with ", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// Check states match
state := r.FormValue("state")
if state != oauthState {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthState, state)
http.Error(w, "invalid oauth state", http.StatusInternalServerError)
return
}
// Delete the state cookie
c := http.Cookie{
Name: oAuth2StateKey,
Domain: m.Hostname,
MaxAge: -1,
}
http.SetCookie(w, &c)
// Perform code exchange
code := r.FormValue("code")
token, err := m.Config.Exchange(context.Background(), code)
if err != nil {
fmt.Printf("Code exchange failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// Get user infos
client := &http.Client{}
req, _ := http.NewRequest("GET", m.UserInfoURL+"?access_token="+token.AccessToken, nil)
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
response, err := client.Do(req)
if err != nil || response.StatusCode == http.StatusBadRequest {
fmt.Printf("User info failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// Get user
var user User
if response.Body == nil {
http.Error(w, "no response body", http.StatusBadRequest)
return
}
////////////////////////////////////////////////
// UNCOMMENT THIS TO DEBUG USERINFO RESPONSE //
// readBody, err := ioutil.ReadAll(response.Body)
// if err != nil {
// panic(err)
// }
// newBody := ioutil.NopCloser(bytes.NewBuffer(readBody))
// response.Body = newBody
// if string(readBody) != "" {
// fmt.Printf("BODY : %q \n", readBody)
// }
////////////////////////////////////////////////
err = json.NewDecoder(response.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Trim the user roles in case they come from LDAP
for key, role := range user.Roles {
user.Roles[key] = strings.TrimPrefix(strings.Split(role, ",")[0], "CN=")
}
// Check if user has the correct role
err = checkUserHasRole(TokenData{User: user}, []string{AdminRole})
if err != nil {
// Log the connexion attempt
log.Printf("| %v (%v %v) | Login failed (Unauthorized user) | %v", user.Login, user.Name, user.Surname, req.RemoteAddr)
http.Redirect(w, r, "/", http.StatusFound)
return
}
// Store the user in cookie
// Generate
xsrfToken, err := common.GenerateRandomString(16)
if err != nil {
http.Error(w, "error generating XSRF Token", http.StatusInternalServerError)
return
}
tokenData := TokenData{User: user, XSRFToken: xsrfToken}
tokens.CreateCookie(tokenData, m.Hostname, authTokenKey, 24*time.Hour, w)
// Log the connexion
log.Printf("| %v (%v %v) | Login success | %v", user.Login, user.Name, user.Surname, req.RemoteAddr)
// Redirect
}
return http.HandlerFunc(oauth2Handler)
}