git.iko.soy/typeable/oauth2-proxy
on remove-gap-headers help sign in
298 lines 8.8 kB history raw
1package providers
3import (
4 "context"
5 "crypto"
6 "crypto/rand"
7 "crypto/rsa"
8 "encoding/json"
9 "net/http"
10 "net/http/httptest"
11 "net/url"
12 "testing"
13 "time"
15 "github.com/dgrijalva/jwt-go"
16 . "github.com/onsi/gomega"
17 "github.com/stretchr/testify/assert"
18 "gopkg.in/square/go-jose.v2"
21type MyKeyData struct {
22 PubKey crypto.PublicKey
23 PrivKey *rsa.PrivateKey
24 PubJWK jose.JSONWebKey
27func newLoginGovServer(body []byte) (*url.URL, *httptest.Server) {
28 s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
29 rw.Write(body)
30 }))
31 u, _ := url.Parse(s.URL)
32 return u, s
35func newLoginGovProvider() (l *LoginGovProvider, serverKey *MyKeyData, err error) {
36 key, err := rsa.GenerateKey(rand.Reader, 2048)
37 if err != nil {
38 return
39 }
40 serverKey = &MyKeyData{
41 PubKey: key.Public(),
42 PrivKey: key,
43 PubJWK: jose.JSONWebKey{
44 Key: key.Public(),
45 KeyID: "testkey",
46 Algorithm: string(jose.RS256),
47 Use: "sig",
48 },
49 }
51 privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
52 if err != nil {
53 return
54 }
56 l = NewLoginGovProvider(
57 &ProviderData{
58 ProviderName: "",
59 LoginURL: &url.URL{},
60 RedeemURL: &url.URL{},
61 ProfileURL: &url.URL{},
62 ValidateURL: &url.URL{},
63 Scope: ""})
64 l.JWTKey = privateKey
65 l.Nonce = "fakenonce"
66 return
69func TestNewLoginGovProvider(t *testing.T) {
70 g := NewWithT(t)
72 // Test that defaults are set when calling for a new provider with nothing set
73 providerData := NewLoginGovProvider(&ProviderData{}).Data()
74 g.Expect(providerData.ProviderName).To(Equal("login.gov"))
75 g.Expect(providerData.LoginURL.String()).To(Equal("https://secure.login.gov/openid_connect/authorize"))
76 g.Expect(providerData.RedeemURL.String()).To(Equal("https://secure.login.gov/api/openid_connect/token"))
77 g.Expect(providerData.ProfileURL.String()).To(Equal("https://secure.login.gov/api/openid_connect/userinfo"))
78 g.Expect(providerData.ValidateURL.String()).To(Equal(""))
79 g.Expect(providerData.Scope).To(Equal("email openid"))
82func TestLoginGovProviderOverrides(t *testing.T) {
83 p := NewLoginGovProvider(
84 &ProviderData{
85 LoginURL: &url.URL{
86 Scheme: "https",
87 Host: "example.com",
88 Path: "/oauth/auth"},
89 RedeemURL: &url.URL{
90 Scheme: "https",
91 Host: "example.com",
92 Path: "/oauth/token"},
93 ProfileURL: &url.URL{
94 Scheme: "https",
95 Host: "example.com",
96 Path: "/oauth/profile"},
97 Scope: "profile"})
98 assert.NotEqual(t, nil, p)
99 assert.Equal(t, "login.gov", p.Data().ProviderName)
100 assert.Equal(t, "https://example.com/oauth/auth",
101 p.Data().LoginURL.String())
102 assert.Equal(t, "https://example.com/oauth/token",
103 p.Data().RedeemURL.String())
104 assert.Equal(t, "https://example.com/oauth/profile",
105 p.Data().ProfileURL.String())
106 assert.Equal(t, "profile", p.Data().Scope)
109func TestLoginGovProviderSessionData(t *testing.T) {
110 p, serverkey, err := newLoginGovProvider()
111 assert.NotEqual(t, nil, p)
112 assert.NoError(t, err)
114 // Set up the redeem endpoint here
115 type loginGovRedeemResponse struct {
116 AccessToken string `json:"access_token"`
117 TokenType string `json:"token_type"`
118 ExpiresIn int64 `json:"expires_in"`
119 IDToken string `json:"id_token"`
120 }
121 expiresIn := int64(60)
122 type MyCustomClaims struct {
123 Acr string `json:"acr"`
124 Nonce string `json:"nonce"`
125 Email string `json:"email"`
126 EmailVerified bool `json:"email_verified"`
127 GivenName string `json:"given_name"`
128 FamilyName string `json:"family_name"`
129 Birthdate string `json:"birthdate"`
130 AtHash string `json:"at_hash"`
131 CHash string `json:"c_hash"`
132 jwt.StandardClaims
133 }
134 claims := MyCustomClaims{
135 "http://idmanagement.gov/ns/assurance/loa/1",
136 "fakenonce",
137 "timothy.spencer@gsa.gov",
138 true,
139 "",
140 "",
141 "",
142 "",
143 "",
144 jwt.StandardClaims{
145 Audience: "Audience",
146 ExpiresAt: time.Now().Unix() + expiresIn,
147 Id: "foo",
148 IssuedAt: time.Now().Unix(),
149 Issuer: "https://idp.int.login.gov",
150 NotBefore: time.Now().Unix() - 1,
151 Subject: "b2d2d115-1d7e-4579-b9d6-f8e84f4f56ca",
152 },
153 }
154 idtoken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
155 signedidtoken, err := idtoken.SignedString(serverkey.PrivKey)
156 assert.NoError(t, err)
157 body, err := json.Marshal(loginGovRedeemResponse{
158 AccessToken: "a1234",
159 TokenType: "Bearer",
160 ExpiresIn: expiresIn,
161 IDToken: signedidtoken,
162 })
163 assert.NoError(t, err)
164 var server *httptest.Server
165 p.RedeemURL, server = newLoginGovServer(body)
166 defer server.Close()
168 // Set up the user endpoint here
169 type loginGovUserResponse struct {
170 Email string `json:"email"`
171 EmailVerified bool `json:"email_verified"`
172 Subject string `json:"sub"`
173 }
174 userbody, err := json.Marshal(loginGovUserResponse{
175 Email: "timothy.spencer@gsa.gov",
176 EmailVerified: true,
177 Subject: "b2d2d115-1d7e-4579-b9d6-f8e84f4f56ca",
178 })
179 assert.NoError(t, err)
180 var userserver *httptest.Server
181 p.ProfileURL, userserver = newLoginGovServer(userbody)
182 defer userserver.Close()
184 // Set up the PubJWKURL endpoint here used to verify the JWT
185 var pubkeys jose.JSONWebKeySet
186 pubkeys.Keys = append(pubkeys.Keys, serverkey.PubJWK)
187 pubjwkbody, err := json.Marshal(pubkeys)
188 assert.NoError(t, err)
189 var pubjwkserver *httptest.Server
190 p.PubJWKURL, pubjwkserver = newLoginGovServer(pubjwkbody)
191 defer pubjwkserver.Close()
193 session, err := p.Redeem(context.Background(), "http://redirect/", "code1234")
194 assert.NoError(t, err)
195 assert.NotEqual(t, session, nil)
196 assert.Equal(t, "timothy.spencer@gsa.gov", session.Email)
197 assert.Equal(t, "a1234", session.AccessToken)
199 // The test ought to run in under 2 seconds. If not, you may need to bump this up.
200 assert.InDelta(t, session.ExpiresOn.Unix(), time.Now().Unix()+expiresIn, 2)
203func TestLoginGovProviderBadNonce(t *testing.T) {
204 p, serverkey, err := newLoginGovProvider()
205 assert.NotEqual(t, nil, p)
206 assert.NoError(t, err)
208 // Set up the redeem endpoint here
209 type loginGovRedeemResponse struct {
210 AccessToken string `json:"access_token"`
211 TokenType string `json:"token_type"`
212 ExpiresIn int64 `json:"expires_in"`
213 IDToken string `json:"id_token"`
214 }
215 expiresIn := int64(60)
216 type MyCustomClaims struct {
217 Acr string `json:"acr"`
218 Nonce string `json:"nonce"`
219 Email string `json:"email"`
220 EmailVerified bool `json:"email_verified"`
221 GivenName string `json:"given_name"`
222 FamilyName string `json:"family_name"`
223 Birthdate string `json:"birthdate"`
224 AtHash string `json:"at_hash"`
225 CHash string `json:"c_hash"`
226 jwt.StandardClaims
227 }
228 claims := MyCustomClaims{
229 "http://idmanagement.gov/ns/assurance/loa/1",
230 "badfakenonce",
231 "timothy.spencer@gsa.gov",
232 true,
233 "",
234 "",
235 "",
236 "",
237 "",
238 jwt.StandardClaims{
239 Audience: "Audience",
240 ExpiresAt: time.Now().Unix() + expiresIn,
241 Id: "foo",
242 IssuedAt: time.Now().Unix(),
243 Issuer: "https://idp.int.login.gov",
244 NotBefore: time.Now().Unix() - 1,
245 Subject: "b2d2d115-1d7e-4579-b9d6-f8e84f4f56ca",
246 },
247 }
248 idtoken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
249 signedidtoken, err := idtoken.SignedString(serverkey.PrivKey)
250 assert.NoError(t, err)
251 body, err := json.Marshal(loginGovRedeemResponse{
252 AccessToken: "a1234",
253 TokenType: "Bearer",
254 ExpiresIn: expiresIn,
255 IDToken: signedidtoken,
256 })
257 assert.NoError(t, err)
258 var server *httptest.Server
259 p.RedeemURL, server = newLoginGovServer(body)
260 defer server.Close()
262 // Set up the user endpoint here
263 type loginGovUserResponse struct {
264 Email string `json:"email"`
265 EmailVerified bool `json:"email_verified"`
266 Subject string `json:"sub"`
267 }
268 userbody, err := json.Marshal(loginGovUserResponse{
269 Email: "timothy.spencer@gsa.gov",
270 EmailVerified: true,
271 Subject: "b2d2d115-1d7e-4579-b9d6-f8e84f4f56ca",
272 })
273 assert.NoError(t, err)
274 var userserver *httptest.Server
275 p.ProfileURL, userserver = newLoginGovServer(userbody)
276 defer userserver.Close()
278 // Set up the PubJWKURL endpoint here used to verify the JWT
279 var pubkeys jose.JSONWebKeySet
280 pubkeys.Keys = append(pubkeys.Keys, serverkey.PubJWK)
281 pubjwkbody, err := json.Marshal(pubkeys)
282 assert.NoError(t, err)
283 var pubjwkserver *httptest.Server
284 p.PubJWKURL, pubjwkserver = newLoginGovServer(pubjwkbody)
285 defer pubjwkserver.Close()
287 _, err = p.Redeem(context.Background(), "http://redirect/", "code1234")
289 // The "badfakenonce" in the idtoken above should cause this to error out
290 assert.Error(t, err)
293func TestLoginGovProviderGetLoginURL(t *testing.T) {
294 p, _, _ := newLoginGovProvider()
295 result := p.GetLoginURL("http://redirect/", "")
296 assert.Contains(t, result, "acr_values="+url.QueryEscape("http://idmanagement.gov/ns/assurance/loa/1"))
297 assert.Contains(t, result, "nonce=fakenonce")