Skip to content

Commit ec0436e

Browse files
Add support for will.iam in maestro (#95)
* Add WilliamAuth feature and tests * Add william handler and middleware * Add handler and middleware tests * Add docs * Bump minor version * Add basicauth.enabled
1 parent b20621a commit ec0436e

31 files changed

+1571
-382
lines changed

api/access_middleware.go

Lines changed: 67 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -7,149 +7,104 @@
77
package api
88

99
import (
10-
"context"
1110
"fmt"
11+
"github.com/topfreegames/extensions/middleware"
12+
"github.com/topfreegames/maestro/api/auth"
1213
"net/http"
1314
"strings"
1415

15-
"github.com/topfreegames/extensions/middleware"
1616
"github.com/topfreegames/maestro/errors"
17-
"github.com/topfreegames/maestro/login"
1817
)
1918

2019
//AccessMiddleware guarantees that the user is logged
2120
type AccessMiddleware struct {
22-
App *App
23-
next http.Handler
24-
enabled bool
21+
App *App
22+
next http.Handler
2523
}
2624

2725
// NewAccessMiddleware returns an access middleware
2826
func NewAccessMiddleware(a *App) *AccessMiddleware {
29-
enabled := a.Config.GetBool("oauth.enabled")
3027
return &AccessMiddleware{
31-
App: a,
32-
enabled: enabled,
33-
}
34-
}
35-
36-
const emailKey = contextKey("emailKey")
37-
38-
func emailFromContext(ctx context.Context) string {
39-
payload := ctx.Value(emailKey)
40-
if payload == nil {
41-
return ""
28+
App: a,
4229
}
43-
return payload.(string)
44-
}
45-
46-
// NewContextWithEmail adds the email from oauth into context
47-
func NewContextWithEmail(ctx context.Context, email string) context.Context {
48-
c := context.WithValue(ctx, emailKey, email)
49-
return c
5030
}
5131

52-
func basicAuthWithXForwardedUserEmail(
53-
basicAuthUser, basicAuthPass string, r *http.Request,
54-
) (string, bool) {
55-
if basicAuthUser == "" && basicAuthPass == "" {
56-
return "", false
57-
}
58-
user, pass, ok := r.BasicAuth()
59-
if !ok || user != basicAuthUser || pass != basicAuthPass {
60-
return "", false
61-
}
62-
email := r.Header.Get("x-forwarded-user-email")
63-
if email == "" {
64-
return "", false
65-
}
66-
return email, true
67-
}
32+
var emptyErr = fmt.Errorf("")
6833

6934
//ServeHTTP methods
7035
func (m *AccessMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
71-
if !m.enabled {
72-
m.next.ServeHTTP(w, r)
73-
return
74-
}
36+
ctx := r.Context()
37+
logger := middleware.GetLogger(ctx)
38+
if m.App.Config.GetBool("basicauth.enabled") {
39+
logger.Debug("checking basic auth")
40+
authPresent, authValid, email := auth.CheckBasicAuth(m.App.Config, r)
41+
42+
// Basic Auth is valid, proceed to next handler
43+
if authValid {
44+
logger.Debug("basic auth ok")
45+
ctx = auth.NewContextWithEmail(ctx, email)
46+
ctx = auth.NewContextWithBasicAuthOK(ctx)
47+
m.next.ServeHTTP(w, r.WithContext(ctx))
48+
return
49+
}
7550

76-
// checking basic auth in case of x-forwarded-user-email
77-
basicAuthUser := m.App.Config.GetString("basicauth.username")
78-
basicAuthPass := m.App.Config.GetString("basicauth.password")
79-
if email, ok := basicAuthWithXForwardedUserEmail(
80-
basicAuthUser, basicAuthPass, r,
81-
); ok {
82-
ctx := NewContextWithEmail(r.Context(), email)
83-
m.next.ServeHTTP(w, r.WithContext(ctx))
84-
return
85-
}
51+
// Basic Auth is invalid, return unauthorized
52+
if authPresent {
53+
logger.Debug("basic auth invalid")
54+
m.App.HandleError(w, http.StatusUnauthorized, "authentication failed", fmt.Errorf("invalid basic auth"))
55+
return
56+
}
8657

87-
logger := middleware.GetLogger(r.Context())
88-
logger.Debug("Checking access token")
58+
logger.Debug("basic auth missing")
59+
// If basic auth is missing and `basicAuth.tryOauthIfUnset` is false return unauthorized
60+
if !m.App.Config.GetBool("basicAuth.tryOauthIfUnset") {
61+
logger.Debug("basic auth tryOauthIfUnset is false so unauthorized")
62+
m.App.HandleError(w, http.StatusUnauthorized, "authentication failed", fmt.Errorf("no basic auth sent"))
63+
return
64+
}
65+
}
8966

90-
accessToken := r.Header.Get("Authorization")
91-
accessToken = strings.TrimPrefix(accessToken, "Bearer ")
67+
if m.App.Config.GetBool("william.enabled") {
68+
logger.Debug("william enabled, checking token")
69+
token := r.Header.Get("Authorization")
70+
if len(token) == 0 {
71+
logger.Debug("token empty")
72+
m.App.HandleError(w, http.StatusUnauthorized, "", errors.NewAccessError("missing access token", emptyErr))
73+
return
74+
}
9275

93-
token, err := login.GetToken(accessToken, m.App.DBClient.WithContext(r.Context()))
94-
if err != nil {
95-
m.App.HandleError(w, http.StatusInternalServerError, "", err)
96-
return
97-
}
98-
if token.RefreshToken == "" {
99-
m.App.HandleError(
100-
w,
101-
http.StatusUnauthorized,
102-
"",
103-
errors.NewAccessError("access token was not found on db", fmt.Errorf("access token error")),
104-
)
105-
return
106-
}
76+
token = strings.TrimPrefix(token, "Bearer ")
10777

108-
msg, status, err := m.App.Login.Authenticate(token, m.App.DBClient.WithContext(r.Context()))
109-
if err != nil {
110-
logger.WithError(err).Error("error fetching googleapis")
111-
m.App.HandleError(w, http.StatusInternalServerError, "Error fetching googleapis", err)
112-
return
113-
}
78+
if len(token) == 0 {
79+
logger.Debug("no bearer token")
80+
m.App.HandleError(w, http.StatusUnauthorized, "", errors.NewAccessError("Unauthorized access token", emptyErr))
81+
return
82+
}
11483

115-
if status == http.StatusBadRequest {
116-
logger.WithError(err).Error("error validating access token")
117-
err := errors.NewAccessError("Unauthorized access token", fmt.Errorf(msg))
118-
m.App.HandleError(w, http.StatusUnauthorized, "Unauthorized access token", err)
119-
return
120-
}
84+
logger.Debug("token received")
85+
} else if m.App.Config.GetBool("oauth.enabled") {
86+
logger.Debug("oauth enabled, checking token")
87+
88+
email, err := auth.CheckOauthToken(m.App.Login, m.App.DBClient.WithContext(ctx), logger, r, m.App.EmailDomains)
89+
if err != nil {
90+
if _, ok := err.(*errors.AccessError); ok {
91+
logger.Debug("authentication invalid")
92+
m.App.HandleError(w, http.StatusUnauthorized, "", err)
93+
} else {
94+
logger.Debug("authentication error")
95+
m.App.HandleError(w, http.StatusInternalServerError, "", err)
96+
}
97+
return
98+
}
12199

122-
if status != http.StatusOK {
123-
logger.WithError(err).Error("invalid access token")
124-
err := errors.NewAccessError("invalid access token", fmt.Errorf(msg))
125-
m.App.HandleError(w, status, "error validating access token", err)
126-
return
127-
}
100+
logger.Debug("token authenticated, putting email on context", email)
128101

129-
email := msg
130-
if !verifyEmailDomain(email, m.App.EmailDomains) {
131-
logger.WithError(err).Error("Invalid email")
132-
err := errors.NewAccessError(
133-
"authorization access error",
134-
fmt.Errorf("the email on OAuth authorization is not from domain %s", m.App.EmailDomains),
135-
)
136-
m.App.HandleError(w, http.StatusUnauthorized, "error validating access token", err)
102+
ctx = auth.NewContextWithEmail(r.Context(), email)
103+
m.next.ServeHTTP(w, r.WithContext(ctx))
137104
return
138105
}
139106

140-
ctx := NewContextWithEmail(r.Context(), email)
141-
142-
logger.Debug("Access token checked")
143-
m.next.ServeHTTP(w, r.WithContext(ctx))
144-
}
145-
146-
func verifyEmailDomain(email string, emailDomains []string) bool {
147-
for _, domain := range emailDomains {
148-
if strings.HasSuffix(email, domain) {
149-
return true
150-
}
151-
}
152-
return false
107+
m.next.ServeHTTP(w, r)
153108
}
154109

155110
//SetNext handler

api/access_middleware_test.go

Lines changed: 66 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -29,70 +29,70 @@ var _ = Describe("AccessMiddleware", func() {
2929
)
3030

3131
yamlString = `{
32-
"name": "scheduler-name",
33-
"game": "game-name",
34-
"image": "somens/someimage:v123",
35-
"ports": [
36-
{
37-
"containerPort": 5050,
38-
"protocol": "UDP",
39-
"name": "port1"
40-
},
41-
{
42-
"containerPort": 8888,
43-
"protocol": "TCP",
44-
"name": "port2"
45-
}
46-
],
47-
"limits": {
48-
"memory": "128Mi",
49-
"cpu": "1"
50-
},
51-
"shutdownTimeout": 180,
52-
"autoscaling": {
53-
"min": 100,
54-
"up": {
55-
"delta": 10,
56-
"trigger": {
57-
"usage": 70,
58-
"time": 600
59-
},
60-
"cooldown": 300
61-
},
62-
"down": {
63-
"delta": 2,
64-
"trigger": {
65-
"usage": 50,
66-
"time": 900
67-
},
68-
"cooldown": 300
69-
}
70-
},
71-
"env": [
72-
{
73-
"name": "EXAMPLE_ENV_VAR",
74-
"value": "examplevalue"
75-
},
76-
{
77-
"name": "ANOTHER_ENV_VAR",
78-
"value": "anothervalue"
79-
}
80-
],
81-
"cmd": [
82-
"./room-binary",
83-
"-serverType",
84-
"6a8e136b-2dc1-417e-bbe8-0f0a2d2df431"
85-
],
86-
"forwarders": {
87-
"mockplugin": {
88-
"mockfwd": {
89-
"enabled": true,
90-
"medatada": {
91-
"send": "me"
92-
}
93-
}
94-
}
95-
}
32+
"name": "scheduler-name",
33+
"game": "game-name",
34+
"image": "somens/someimage:v123",
35+
"ports": [
36+
{
37+
"containerPort": 5050,
38+
"protocol": "UDP",
39+
"name": "port1"
40+
},
41+
{
42+
"containerPort": 8888,
43+
"protocol": "TCP",
44+
"name": "port2"
45+
}
46+
],
47+
"limits": {
48+
"memory": "128Mi",
49+
"cpu": "1"
50+
},
51+
"shutdownTimeout": 180,
52+
"autoscaling": {
53+
"min": 100,
54+
"up": {
55+
"delta": 10,
56+
"trigger": {
57+
"usage": 70,
58+
"time": 600
59+
},
60+
"cooldown": 300
61+
},
62+
"down": {
63+
"delta": 2,
64+
"trigger": {
65+
"usage": 50,
66+
"time": 900
67+
},
68+
"cooldown": 300
69+
}
70+
},
71+
"env": [
72+
{
73+
"name": "EXAMPLE_ENV_VAR",
74+
"value": "examplevalue"
75+
},
76+
{
77+
"name": "ANOTHER_ENV_VAR",
78+
"value": "anothervalue"
79+
}
80+
],
81+
"cmd": [
82+
"./room-binary",
83+
"-serverType",
84+
"6a8e136b-2dc1-417e-bbe8-0f0a2d2df431"
85+
],
86+
"forwarders": {
87+
"mockplugin": {
88+
"mockfwd": {
89+
"enabled": true,
90+
"medatada": {
91+
"send": "me"
92+
}
93+
}
94+
}
95+
}
9696
}`
9797

9898
BeforeEach(func() {
@@ -123,6 +123,8 @@ var _ = Describe("AccessMiddleware", func() {
123123

124124
url := fmt.Sprintf("http://%s/scheduler", app.Address)
125125
request, err := http.NewRequest("GET", url, nil)
126+
Expect(err).ToNot(HaveOccurred())
127+
126128
user := app.Config.GetString("basicauth.username")
127129
pass := app.Config.GetString("basicauth.password")
128130
request.Header.Add("X-Forwarded-User-Email", "[email protected]")

0 commit comments

Comments
 (0)