1 package okta
2
3 import (
4 "fmt"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "strings"
9 "time"
10
11 "edge-infra.dev/pkg/edge/api/types"
12 "edge-infra.dev/pkg/edge/api/utils"
13 )
14
15 const (
16 oktaIntrospectionPath = "/v1/introspect?client_id=%s"
17 oktaUserInfoPath = "/v1/userinfo"
18 oktaTokenRefreshPath = "/v1/token"
19 badOktaToken = "bad-okta-token"
20 invalidOktaToken = "invalid-okta-token"
21 )
22
23 func MockServer(cfg *types.OktaConfig) *httptest.Server {
24 server := utils.NewMockHTTPTestServer().AddAllowedContentType(formContentType, jsonContentType).DefaultNotFound()
25
26 server.Post("Okta Introspection", fmt.Sprintf(oktaIntrospectionPath, cfg.ClientID), func(w http.ResponseWriter, r *http.Request) {
27 requestBody := make(map[string]string)
28 err := utils.ReadRequestBody(r, requestBody)
29 if err != nil {
30 panic(err)
31 }
32 if _, exists := requestBody["token"]; !exists || requestBody["token"] == badOktaToken {
33 utils.WriteCustomResponse(w, http.StatusUnauthorized, []byte("Invalid Credentials"))
34 } else if _, exists := requestBody["token"]; !exists || requestBody["token"] == invalidOktaToken {
35 response := MockIntrospectionInactiveResponse(server.Server.URL, cfg.ClientID)
36 utils.WriteJSON(w, response)
37 } else {
38 response := MockIntrospectionResponse(server.Server.URL, cfg.ClientID)
39 utils.WriteJSON(w, response)
40 }
41 }, func(_ http.ResponseWriter, r *http.Request) bool {
42 queries, err := url.ParseQuery(r.URL.RawQuery)
43 if err != nil {
44 return false
45 }
46 val, exists := queries["client_id"]
47 if exists {
48 return val[0] == cfg.ClientID
49 }
50 return exists
51 })
52
53 server.Get("Okta UserInfo", oktaUserInfoPath, func(w http.ResponseWriter, r *http.Request) {
54 if _, exists := r.Header["Authorization"]; !exists || strings.Contains(r.Header["Authorization"][0], badOktaToken) {
55 utils.WriteCustomResponse(w, http.StatusUnauthorized, []byte("Invalid Credentials"))
56 } else {
57 response := MockUserInfoResponse()
58 utils.WriteJSON(w, response)
59 }
60 }, func(_ http.ResponseWriter, r *http.Request) bool {
61 return r.Method == http.MethodGet
62 })
63 server.Post("Okta Token Refresh", oktaTokenRefreshPath, func(w http.ResponseWriter, _ *http.Request) {
64 response := MockTokenRefreshResponse()
65 utils.WriteJSON(w, response)
66 }, func(_ http.ResponseWriter, r *http.Request) bool {
67 return r.Method == http.MethodPost && strings.Contains(r.RequestURI, oktaTokenRefreshPath)
68 })
69 cfg.OktaIssuer = server.Server.URL
70 return server.Server
71 }
72
73 func MockIntrospectionResponse(issuer, clientID string) *IntrospectionResponse {
74 currentTime := time.Now()
75 return &IntrospectionResponse{
76 Active: true,
77 Scope: "profile email openid",
78 Username: "test-user",
79 Expires: int(currentTime.Add(15 * time.Minute).UnixNano()),
80 IssuedAt: int(currentTime.UnixNano()),
81 Sub: "test@ncr.com",
82 Aud: "api://default",
83 Issuer: issuer,
84 Jti: "AT.IR2_7zD62pLUDqsddXeccrcpf98LvhjnsqKlsIgnXcNFFK2s",
85 TokenType: "Bearer",
86 ClientID: clientID,
87 UID: "vguhjsbxvksbwvdid",
88 Groups: []string{"Everyone"},
89 Type: "user",
90 Email: "test@ncr.com",
91 }
92 }
93
94 func MockIntrospectionInactiveResponse(issuer, clientID string) *IntrospectionResponse {
95 currentTime := time.Now()
96 return &IntrospectionResponse{
97 Active: false,
98 Scope: "profile email openid",
99 Username: "test-user",
100 Expires: int(currentTime.Add(15 * time.Minute).UnixNano()),
101 IssuedAt: int(currentTime.UnixNano()),
102 Sub: "test@ncr.com",
103 Aud: "api://default",
104 Issuer: issuer,
105 Jti: "AT.IR2_7zD62pLUDqsddXeccrcpf98LvhjnsqKlsIgnXcNFFK2s",
106 TokenType: "Bearer",
107 ClientID: clientID,
108 UID: "vguhjsbxvksbwvdid",
109 Groups: []string{"Everyone"},
110 Type: "user",
111 Email: "test@ncr.com",
112 }
113 }
114
115 func MockUserInfoResponse() *UserInfo {
116 return &UserInfo{
117 Sub: "test@ncr.com",
118 Name: "John Doe",
119 Locale: "en_US",
120 Email: "test@ncr.com",
121 PreferredUsername: "test@ncr.com",
122 GivenName: "John",
123 FamilyName: "Doe",
124 ZoneInfo: "America/Los_Angeles",
125 EmailVerified: true,
126 }
127 }
128
129 func MockTokenRefreshResponse() *RefreshResponse {
130 return &RefreshResponse{
131 TokenType: "refresh-token",
132 ExpiresIn: float64(15 * time.Minute),
133 AccessToken: "good-okta-token",
134 Scope: "offline_access openid",
135 RefreshToken: "refresh-token",
136 IDToken: "id-token",
137 }
138 }
139
View as plain text