├── .gitignore ├── LICENSE ├── README.md └── oauth2 └── server ├── error.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the author nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-oauth2-server 2 | ================ 3 | 4 | An OAuth2 provider in golang. 5 | 6 | INCOMPLETE. -------------------------------------------------------------------------------- /oauth2/server/error.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | type errorCode string 4 | 5 | const ( 6 | // Error codes returned by the server, following the OAuth specification. 7 | ErrorCodeAccessDenied errorCode = "access_denied" 8 | ErrorCodeInvalidRequest errorCode = "invalid_request" 9 | ErrorCodeInvalidScope errorCode = "invalid_scope" 10 | ErrorCodeServerError errorCode = "server_error" 11 | ErrorCodeTemporarilyUnavailable errorCode = "temporarily_unavailable" 12 | ErrorCodeunauthorizedClient errorCode = "unauthorized_client" 13 | ErrorCodeUnsupportedResponseType errorCode = "unsupported_response_type" 14 | ) 15 | 16 | // NewServerError [...] 17 | func NewServerError(code errorCode, description, uri string) ServerError { 18 | return ServerError{code, description, uri} 19 | } 20 | 21 | // ServerError [...] 22 | type ServerError struct { 23 | code errorCode 24 | description string 25 | uri string 26 | } 27 | 28 | // Error [...] 29 | func (e ServerError) Error() string { 30 | return string(e.code) 31 | } 32 | 33 | // Code [...] 34 | func (e ServerError) Code() errorCode { 35 | return e.code 36 | } 37 | 38 | // Description [...] 39 | func (e ServerError) Description() string { 40 | return e.description 41 | } 42 | 43 | // URI [...] 44 | func (e ServerError) URI() string { 45 | return e.uri 46 | } 47 | -------------------------------------------------------------------------------- /oauth2/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // ---------------------------------------------------------------------------- 13 | 14 | // Store [...] 15 | type Store interface { 16 | // A Client is always returned -- it is nil only if ClientID is invalid. 17 | // Use the error to indicate denied or unauthorized access. 18 | GetClient(clientID string) (Client, error) 19 | CreateAuthCode(r AuthCodeRequest) (string, error) 20 | } 21 | 22 | // ---------------------------------------------------------------------------- 23 | 24 | // Client is a client registered with the authorization server. 25 | type Client interface { 26 | // Unique identifier for the client. 27 | ID() string 28 | // The registered client type ("confidential" or "public") as decribed in: 29 | // http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-2.1 30 | Type() string 31 | // The registered redirect_uri. 32 | RedirectURI() string 33 | // Validates that the provided redirect_uri is valid. It must return the 34 | // same provided URI or an empty string if it is not valid. 35 | // The specification is permissive and even allows multiple URIs, so the 36 | // validation rules are up to the server implementation. 37 | // Ref: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.1.2.2 38 | ValidateRedirectURI(string) string 39 | } 40 | 41 | // ---------------------------------------------------------------------------- 42 | 43 | // AuthCodeRequest [...] 44 | type AuthCodeRequest struct { 45 | ClientID string 46 | ResponseType string 47 | RedirectURI string 48 | Scope string 49 | State string 50 | } 51 | 52 | // AccessTokenRequest [...] 53 | type AccessTokenRequest struct { 54 | GrantType string 55 | Code string 56 | RedirectURI string 57 | } 58 | 59 | // ---------------------------------------------------------------------------- 60 | 61 | // NewServer [...] 62 | func NewServer() *Server { 63 | return &Server{ 64 | errorURIs: make(map[errorCode]string), 65 | } 66 | } 67 | 68 | // Server [...] 69 | type Server struct { 70 | Store Store 71 | errorURIs map[errorCode]string 72 | } 73 | 74 | // RegisterErrorURI [...] 75 | func (s *Server) RegisterErrorURI(code errorCode, uri string) { 76 | s.errorURIs[code] = uri 77 | } 78 | 79 | // NewError [...] 80 | func (s *Server) NewError(code errorCode, description string) ServerError { 81 | return NewServerError(code, description, s.errorURIs[code]) 82 | } 83 | 84 | // NewAuthCodeRequest [...] 85 | func (s *Server) NewAuthCodeRequest(r *http.Request) AuthCodeRequest { 86 | v := r.URL.Query() 87 | return AuthCodeRequest{ 88 | ClientID: v.Get("client_id"), 89 | ResponseType: v.Get("response_type"), 90 | RedirectURI: v.Get("redirect_uri"), 91 | Scope: v.Get("scope"), 92 | State: v.Get("state"), 93 | } 94 | } 95 | 96 | // HandleAuthCodeRequest [...] 97 | func (s *Server) HandleAuthCodeRequest(w http.ResponseWriter, r *http.Request) error { 98 | // 1. Get all request values. 99 | req := s.NewAuthCodeRequest(r) 100 | 101 | // 2. Validate required parameters. 102 | var err error 103 | if req.ClientID == "" { 104 | // Missing ClientID: no redirect. 105 | err = s.NewError(ErrorCodeInvalidRequest, 106 | "The \"client_id\" parameter is missing.") 107 | } else if req.ResponseType == "" { 108 | err = s.NewError(ErrorCodeInvalidRequest, 109 | "The \"response_type\" parameter is missing.") 110 | } else if req.ResponseType != "code" { 111 | err = s.NewError(ErrorCodeUnsupportedResponseType, 112 | fmt.Sprintf("The response type %q is not supported.", 113 | req.ResponseType)) 114 | } 115 | 116 | // 3. Load client and validate the redirection URI. 117 | var redirectURI *url.URL 118 | if req.ClientID != "" { 119 | client, clientErr := s.Store.GetClient(req.ClientID) 120 | if client == nil { 121 | // Invalid ClientID: no redirect. 122 | if err == nil { 123 | err = s.NewError(ErrorCodeInvalidRequest, 124 | "The \"client_id\" parameter is invalid.") 125 | } 126 | } else { 127 | if u, uErr := validateRedirectURI( 128 | client.ValidateRedirectURI(req.RedirectURI)); uErr == nil { 129 | redirectURI = u 130 | } else { 131 | // Missing, mismatching or invalid URI: no redirect. 132 | if err == nil { 133 | if req.RedirectURI == "" { 134 | err = s.NewError(ErrorCodeInvalidRequest, 135 | "Missing redirection URI.") 136 | } else { 137 | err = s.NewError(ErrorCodeInvalidRequest, uErr.Error()) 138 | } 139 | } 140 | } 141 | if clientErr != nil && err == nil { 142 | // Client was not authorized. 143 | err = clientErr 144 | } 145 | } 146 | } 147 | 148 | // 4. If no valid redirection URI was set, abort. 149 | if redirectURI == nil { 150 | // An error occurred because client_id or redirect_uri are invalid: 151 | // the caller must display an error page and don't redirect. 152 | return err 153 | } 154 | 155 | // 5. Add the response data to the URL and redirect. 156 | query := redirectURI.Query() 157 | setQueryPairs(query, "state", req.State) 158 | var code string 159 | if err == nil { 160 | code, err = s.Store.CreateAuthCode(req) 161 | } 162 | if err == nil { 163 | // Success. 164 | query.Set("code", code) 165 | } else { 166 | e, ok := err.(ServerError) 167 | if !ok { 168 | e = s.NewError(ErrorCodeServerError, e.Error()) 169 | } 170 | setQueryPairs(query, 171 | "error", string(e.Code()), 172 | "error_description", e.Description(), 173 | "error_uri", e.URI(), 174 | ) 175 | } 176 | redirectURI.RawQuery = query.Encode() 177 | http.Redirect(w, r, redirectURI.String(), 302) 178 | return nil 179 | } 180 | 181 | // ---------------------------------------------------------------------------- 182 | 183 | // setQueryPairs sets non-empty values in a url.Values. 184 | // 185 | // This is just a convenience to avoid checking for emptiness for each value. 186 | func setQueryPairs(v url.Values, pairs ...string) { 187 | for i := 0; i < len(pairs); i += 2 { 188 | if pairs[i+1] != "" { 189 | v.Set(pairs[i], pairs[i+1]) 190 | } 191 | } 192 | } 193 | 194 | // validateRedirectURI checks if a redirection URL is valid. 195 | func validateRedirectURI(uri string) (u *url.URL, err error) { 196 | u, err = url.Parse(uri) 197 | if err != nil { 198 | err = fmt.Errorf("The redirection URI is malformed: %q.", uri) 199 | } else if !u.IsAbs() { 200 | err = fmt.Errorf("The redirection URI must be absolute: %q.", uri) 201 | } else if u.Fragment != "" { 202 | err = fmt.Errorf( 203 | "The redirection URI must not contain a fragment: %q.", uri) 204 | } 205 | return 206 | } 207 | 208 | // randomString generates authorization codes or tokens with a given strength. 209 | func randomString(strength int) string { 210 | s := make([]byte, strength) 211 | if _, err := rand.Read(s); err != nil { 212 | return "" 213 | } 214 | return strings.TrimRight(base64.URLEncoding.EncodeToString(s), "=") 215 | } 216 | --------------------------------------------------------------------------------