summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeroen Wijenbergh <jeroenwijenbergh@protonmail.com>2022-03-08 13:13:14 +0100
committerjwijenbergh <jeroenwijenbergh@protonmail.com>2022-04-05 12:26:13 +0200
commit5065de4cff907b70ea3446888a7bad243744a8ab (patch)
tree32c610aca2865426415040c324beb2a2a52db756 /src
parente2bcbc5d7fc8846ed189863ab33f0514f5399365 (diff)
OAuth: Begin implementation without OAuth2 lib
- We want to use as little dependencies as possible. While the OAuth2 library is helpful, it is not needed.
Diffstat (limited to 'src')
-rw-r--r--src/api.go19
-rw-r--r--src/oauth.go130
-rw-r--r--src/state.go10
3 files changed, 105 insertions, 54 deletions
diff --git a/src/api.go b/src/api.go
index 8bd2847..6a4596a 100644
--- a/src/api.go
+++ b/src/api.go
@@ -8,9 +8,9 @@ import (
)
type endpointList struct {
- Endpoint string `json:"api_endpoint"`
- AuthorizationEndpoint string `json:"authorization_endpoint"`
- TokenEndpoint string `json:"token_endpoint"`
+ API string `json:"api_endpoint"`
+ Authorization string `json:"authorization_endpoint"`
+ Token string `json:"token_endpoint"`
}
// Struct that defines the json format for /.well-known/vpn-user-portal"
@@ -52,8 +52,16 @@ func APIGetEndpoints(vpnState *EduVPNState) (*EduVPNEndpoints, error) {
}
func APIAuthenticatedInfo(vpnState *EduVPNState) (string, error) {
- url := vpnState.Endpoints.API.V3.Endpoint + "/info"
- resp, reqErr := vpnState.OAuth.client.Get(url)
+ url := vpnState.Endpoints.API.V3.API + "/info"
+
+ client := &http.Client{}
+ req, reqErr := http.NewRequest(http.MethodGet, url, nil)
+ if reqErr != nil {
+ return "", reqErr
+ }
+ req.Header.Add("Authorization", "Bearer "+vpnState.OAuthToken.Access)
+ resp, reqErr := client.Do(req)
+
if reqErr != nil {
return "", reqErr
}
@@ -64,6 +72,7 @@ func APIAuthenticatedInfo(vpnState *EduVPNState) (string, error) {
if resp.StatusCode != http.StatusOK {
return "", errors.New("HTTP code not ok")
}
+
// Read the body
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
diff --git a/src/oauth.go b/src/oauth.go
index 87284a2..6212124 100644
--- a/src/oauth.go
+++ b/src/oauth.go
@@ -4,9 +4,12 @@ import (
"context"
"crypto/sha256"
"encoding/base64"
+ "encoding/json"
"fmt"
- "golang.org/x/oauth2"
+ "io/ioutil"
"net/http"
+ "net/url"
+ "strings"
)
// Generates a random base64 string to be used for state
@@ -52,22 +55,29 @@ func genVerifier() (string, error) {
}
// This structure gets passed to the callback for easy access to the current state
-type EduVPNOauth struct {
+type EduVPNOAuthSession struct {
// Public
- AuthURL string
- Config *oauth2.Config
+ AuthURL string
+ VPNState *EduVPNState
// private
callbackError error
- client *http.Client
context context.Context
state string
server *http.Server
verifier string
}
+// Struct that defines the json format for /.well-known/vpn-user-portal"
+type EduVPNOAuthToken struct {
+ Access string `json:"access_token"`
+ Refresh string `json:"refresh_token"`
+ Type string `json:"token_type"`
+ Expires int `json:"expires_in"`
+}
+
// Gets an authenticated HTTP client by obtaining refresh and access tokens
-func (eduvpn *EduVPNOauth) getHTTPTokenClient() error {
+func (eduvpn *EduVPNOAuthSession) getHTTPTokenClient() error {
eduvpn.context = context.Background()
mux := http.NewServeMux()
eduvpn.server = &http.Server{
@@ -84,26 +94,53 @@ func (eduvpn *EduVPNOauth) getHTTPTokenClient() error {
// Get the access and refresh tokens
// Access tokens: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.4
// Refresh tokens: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.3.2
-func (eduvpn *EduVPNOauth) getTokens(authCode string) error {
+func (eduvpn *EduVPNOAuthSession) getTokens(authCode string) error {
// Make sure the verifier is set as the parameter
// so that the server can verify that we are the actual owner of the authorization code
- codeVerifier := oauth2.SetAuthURLParam("code_verifier", eduvpn.verifier)
- // This is from the oauth library that gets the tokens from the authcode
- // https://pkg.go.dev/golang.org/x/oauth2#Config.Exchange
- // We pass the verifier as an additional parameter
- tok, err := eduvpn.Config.Exchange(eduvpn.context, authCode, codeVerifier)
- if err != nil {
- return err
+ data := url.Values{
+ "client_id": {eduvpn.VPNState.Name},
+ "code": {authCode},
+ "code_verifier": {eduvpn.verifier},
+ "grant_type": {"authorization_code"},
+ "redirect_uri": {"http://127.0.0.1:8000/callback"},
+ }
+ client := &http.Client{}
+ req, reqErr := http.NewRequest(http.MethodPost, eduvpn.VPNState.Endpoints.API.V3.Token, strings.NewReader(data.Encode()))
+ if reqErr != nil {
+ return reqErr
}
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+ resp, reqErr := client.Do(req)
+
+ if reqErr != nil {
+ return reqErr
+ }
+
+ // Close the response body at the end
+ defer resp.Body.Close()
+
+ // Read the body
+ body, readErr := ioutil.ReadAll(resp.Body)
+ if readErr != nil {
+ return readErr
+ }
+
+ tokenStructure := &EduVPNOAuthToken{}
+ jsonErr := json.Unmarshal(body, tokenStructure)
+
+ if jsonErr != nil {
+ return jsonErr
+ }
+
+ eduvpn.VPNState.OAuthToken = tokenStructure
- // Fill the http client for future use in the struct
- eduvpn.client = eduvpn.Config.Client(eduvpn.context, tok)
return nil
}
-// The callback to retrieve the authorization code: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.3.1
-func (eduvpn *EduVPNOauth) oauthCallback(w http.ResponseWriter, req *http.Request) {
+//
+//// The callback to retrieve the authorization code: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04#section-1.3.1
+func (eduvpn *EduVPNOAuthSession) oauthCallback(w http.ResponseWriter, req *http.Request) {
// Extract the authorization code
code, success := req.URL.Query()["code"]
if !success {
@@ -144,19 +181,20 @@ func (eduvpn *EduVPNOauth) oauthCallback(w http.ResponseWriter, req *http.Reques
go eduvpn.server.Shutdown(eduvpn.context)
}
-// Generate a config for oauth
-// It uses the state to get the server and the name
-func genConfig(vpnState *EduVPNState) (*oauth2.Config, error) {
- config := &oauth2.Config{
- RedirectURL: "http://127.0.0.1:8000/callback",
- ClientID: vpnState.Name,
- Scopes: []string{"config"},
- Endpoint: oauth2.Endpoint{
- AuthURL: vpnState.Endpoints.API.V3.AuthorizationEndpoint,
- TokenURL: vpnState.Endpoints.API.V3.TokenEndpoint,
- },
- }
- return config, nil
+func constructURL(baseURL string, parameters map[string]string) (string, error) {
+ url, err := url.Parse(baseURL)
+
+ if err != nil {
+ return "", err
+ }
+
+ q := url.Query()
+
+ for parameter, value := range parameters {
+ q.Set(parameter, value)
+ }
+ url.RawQuery = q.Encode()
+ return url.String(), nil
}
// Initializes the OAuth for eduvpn.
@@ -167,11 +205,6 @@ func InitializeOAuth(vpnState *EduVPNState) (string, error) {
panic("invalid state")
}
- config, configErr := genConfig(vpnState)
- if configErr != nil {
- return "", configErr
- }
-
// Generate the state
state, stateErr := genState()
if stateErr != nil {
@@ -185,13 +218,24 @@ func InitializeOAuth(vpnState *EduVPNState) (string, error) {
}
challenge := genChallengeS256(verifier)
- // Update the auth url with the challenge and state
- codeChallengeMethod := oauth2.SetAuthURLParam("code_challenge_method", "S256")
- codeChallenge := oauth2.SetAuthURLParam("code_challenge", challenge)
- authURL := config.AuthCodeURL(state, codeChallengeMethod, codeChallenge)
+ parameters := map[string]string{
+ "client_id": vpnState.Name,
+ "code_challenge_method": "S256",
+ "code_challenge": challenge,
+ "response_type": "code",
+ "scope": "config",
+ "state": state,
+ "redirect_uri": "http://127.0.0.1:8000/callback",
+ }
+
+ authURL, urlErr := constructURL(vpnState.Endpoints.API.V3.Authorization, parameters)
+
+ if urlErr != nil {
+ panic(urlErr)
+ }
// Fill the struct with the necessary fields filled for the next call to getting the HTTP client
- vpnState.OAuth = &EduVPNOauth{AuthURL: authURL, Config: config, state: state, verifier: verifier}
+ vpnState.OAuthSession = &EduVPNOAuthSession{AuthURL: authURL, VPNState: vpnState, state: state, verifier: verifier}
return authURL, nil
}
@@ -200,10 +244,10 @@ func FinishOAuth(vpnState *EduVPNState) error {
panic("invalid state")
}
- if vpnState.OAuth == nil {
+ if vpnState.OAuthSession == nil {
panic("invalid oauth state")
}
- return vpnState.OAuth.getHTTPTokenClient()
+ return vpnState.OAuthSession.getHTTPTokenClient()
}
// OAuthErrorCode Simplified error code for public interface.
diff --git a/src/state.go b/src/state.go
index 22dcaaf..5d054bb 100644
--- a/src/state.go
+++ b/src/state.go
@@ -1,15 +1,16 @@
package eduvpn
type EduVPNState struct {
- // The struct used for oauth
- OAuth *EduVPNOauth
-
// The endpoints
Endpoints *EduVPNEndpoints
// Info passed by the client
Name string
Server string
+
+ // OAuth
+ OAuthToken *EduVPNOAuthToken
+ OAuthSession *EduVPNOAuthSession
}
func Register(state *EduVPNState, name string, server string) error {
@@ -26,7 +27,6 @@ func Register(state *EduVPNState, name string, server string) error {
return nil
}
-
var VPNStateInstance *EduVPNState
func GetVPNState() *EduVPNState {
@@ -35,5 +35,3 @@ func GetVPNState() *EduVPNState {
}
return VPNStateInstance
}
-
-