-
Notifications
You must be signed in to change notification settings - Fork 0
/
oauth2.go
247 lines (212 loc) · 7.05 KB
/
oauth2.go
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
53
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package oauth2
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/gorilla/schema"
)
type GrantType string
const (
GrantTypeAuthorizationCode GrantType = "authorization_code"
GrantTypeClientCredentials GrantType = "client_credentials"
GrantTypePassword GrantType = "password"
GrantTypeRefreshToken GrantType = "refresh_token"
GrantTypeUnknown GrantType = ""
)
func GrantTypeFromString(s string) GrantType {
switch s {
case string(GrantTypeAuthorizationCode):
return GrantTypeAuthorizationCode
case string(GrantTypeClientCredentials):
return GrantTypeClientCredentials
case string(GrantTypePassword):
return GrantTypePassword
case string(GrantTypeRefreshToken):
return GrantTypeRefreshToken
}
return GrantTypeUnknown
}
type ErrorCode string
const (
// 4.1.2.1, 4.2.2.1, 5.2
ErrorInvalidRequest ErrorCode = "invalid_request"
// 4.1.2.1, 4.2.2.1, 5.2
ErrorUnauthorizedClient ErrorCode = "unauthorized_client"
// 4.1.2.1, 4.2.2.1
ErrorAccessDenied ErrorCode = "access_denied"
// 4.1.2.1, 4.2.2.1
ErrorUnspportedResponseType ErrorCode = "unsupported_response_type"
// 4.1.2.1, 4.2.2.1, 5.2
ErrorInvalidScope ErrorCode = "invalid_scope"
// 4.1.2.1, 4.2.2.1
ErrorServerError ErrorCode = "server_error"
// 4.1.2.1, 4.2.2.1
ErrorTemporarilyUnavailable ErrorCode = "temporarily_unavailable"
// 5.2
ErrorInvalidClient ErrorCode = "invalid_client"
// 5.2
ErrorInvalidGrant ErrorCode = "invalid_grant"
// 5.2
ErrorUnsupportedGrantType ErrorCode = "unsupported_grant_type"
)
func (errorCode ErrorCode) HTTPStatusCode() int {
switch errorCode {
case ErrorInvalidRequest,
ErrorInvalidClient,
ErrorInvalidGrant,
ErrorUnauthorizedClient,
ErrorUnsupportedGrantType:
return http.StatusBadRequest
}
return http.StatusInternalServerError
}
type ResponseType string
const (
ResponseTypeCode ResponseType = "code"
ResponseTypeToken ResponseType = "token"
ResponseTypeUnknown ResponseType = ""
)
func ResponseTypeFromString(s string) ResponseType {
switch s {
case string(ResponseTypeCode):
return ResponseTypeCode
case string(ResponseTypeToken):
return ResponseTypeToken
}
return ResponseTypeUnknown
}
func (responseType ResponseType) String() string { return string(responseType) }
type TokenType string
//NOTE: token types are case-insensitive
const (
TokenTypeBearer TokenType = "bearer"
)
// TokenResponse is used on successful authorization. The authorization
// server issues an access token and optional refresh
// token, and constructs the response by adding the following parameters
// to the entity-body of the HTTP response with a 200 (OK) status code
type TokenResponse struct {
// The access token issued by the authorization server.
AccessToken string `json:"access_token" schema:"access_token"`
// The type of the token issued as described in
// Section 7.1. Value is case insensitive.
TokenType TokenType `json:"token_type" schema:"token_type"`
// The lifetime in seconds of the access token. For
// example, the value "3600" denotes that the access token will
// expire in one hour from the time the response was generated.
// If omitted, the authorization server SHOULD provide the
// expiration time via other means or document the default value.
ExpiresIn int64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"`
// The refresh token, which can be used to obtain new
// access tokens using the same authorization grant as described
// in Section 6.
RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"`
// The scope of the access token as described by Section 3.3.
Scope string `json:"scope,omitempty" schema:"scope,omitempty"`
State string `json:"-" schema:"state,omitempty"`
}
func (TokenResponse) SwaggerDoc() map[string]string {
return map[string]string{
"": "See https://tools.ietf.org/html/rfc6749#section-5.1 for details.",
}
}
type ErrorResponse struct {
Error ErrorCode `json:"error" schema:"error"`
ErrorDescription string `json:"error_description,omitempty" schema:"error_description,omitempty"`
ErrorURI string `json:"error_uri,omitempty" schema:"error_uri,omitempty"`
State string `json:"-" schema:"state,omitempty"`
}
func (ErrorResponse) SwaggerDoc() map[string]string {
return map[string]string{
"": "See https://tools.ietf.org/html/rfc6749#section-5.2 for details.",
}
}
type AuthorizationRequest struct {
ResponseType string `schema:"response_type"`
ClientID string `schema:"client_id"`
RedirectURI string `schema:"redirect_uri,omitempty"`
Scope string `schema:"scope,omitepmty"`
State string `schema:"state,omitempty"`
}
func AuthorizationRequestFromURLValues(values url.Values) (*AuthorizationRequest, error) {
var req AuthorizationRequest
err := schemaDecoder.Decode(&req, values)
if err != nil {
return nil, err
}
return &req, nil
}
type AuthorizationResponse struct {
Code string `schema:"code"`
State string `schema:"state"`
}
type AccessTokenRequest struct {
GrantType GrantType `schema:"grant_type"`
// Code is required in 'code' flow
Code string `schema:"code"`
// Username is required in 'password' flow
Username string `schema:"username"`
// Password is used in 'password' flow
Password string `schema:"password"`
}
func QueryString(d interface{}) (queryString string, err error) {
values := url.Values{}
err = schemaEncoder.Encode(d, values)
if err != nil {
return "", err
}
return values.Encode(), nil
}
func MustQueryString(d interface{}) string {
s, err := QueryString(d)
if err != nil {
panic(err)
}
return s
}
var (
schemaEncoder = schema.NewEncoder()
schemaDecoder = schema.NewDecoder()
)
func RespondTo(w http.ResponseWriter) Responder { return Responder{w} }
type Responder struct {
w http.ResponseWriter
}
// ErrInvalidClientBasicAuthorization is used for special case for ErrorInvalidClient.
// Instead of returning 400, we respond with 401 and provide information
// about the client authorizations supported, for this case, Basic.
//
// Details: RFC 6749 § 5.2
func (r Responder) ErrInvalidClientBasicAuthorization(realmName string, errorDesc string) {
if realmName == "" {
realmName = "Restricted"
}
r.w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realmName))
r.ErrorWithHTTPStatusCode(ErrorResponse{
Error: ErrorInvalidClient,
ErrorDescription: errorDesc,
}, http.StatusUnauthorized)
}
func (r Responder) Error(errorData ErrorResponse) {
r.ErrorWithHTTPStatusCode(errorData, errorData.Error.HTTPStatusCode())
}
func (r Responder) ErrorCode(errorCode ErrorCode) {
r.ErrorWithHTTPStatusCode(ErrorResponse{Error: errorCode}, errorCode.HTTPStatusCode())
}
func (r Responder) ErrorWithHTTPStatusCode(errorData ErrorResponse, httpStatusCode int) {
r.w.Header().Set("Content-Type", "application/json")
r.w.WriteHeader(httpStatusCode)
err := json.NewEncoder(r.w).Encode(errorData)
if err != nil {
panic(err)
}
}
func (r Responder) TokenCustom(tokenData interface{}) {
r.w.Header().Set("Content-Type", "application/json")
r.w.WriteHeader(http.StatusOK)
err := json.NewEncoder(r.w).Encode(tokenData)
if err != nil {
panic(err)
}
}