oauth2/oauth2client/client.go

154 lines
3.6 KiB
Go
Executable File

package oauth2client
import (
"crypto/tls"
"errors"
"local/oauth2"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
type cached struct {
access string
exp time.Time
}
var cache = map[string]cached{}
func Authenticate(server, scope string, w http.ResponseWriter, r *http.Request) error {
oauth2server, err := url.Parse(server)
if err != nil {
return err
}
access, exists := findAccess(w, r)
if !exists {
return login(oauth2server, scope, w, r)
}
return verify(access, oauth2server, scope, w, r)
}
func findAccess(w http.ResponseWriter, r *http.Request) (string, bool) {
fresh, exists := findAccessFresh(w, r)
if exists {
return fresh, true
}
stable, exists := findAccessStable(w, r)
return stable, exists
}
func findAccessFresh(w http.ResponseWriter, r *http.Request) (string, bool) {
access, found := findAccessFreshQueryParam(w, r)
if !found {
access, found = findAccessFreshCookie(w, r)
}
if found {
setCookie(oauth2.COOKIE, access, "", w)
}
return access, found
}
func findAccessFreshQueryParam(w http.ResponseWriter, r *http.Request) (string, bool) {
q := r.URL.Query()
access := q.Get(oauth2.NEWCOOKIE)
q.Del(oauth2.NEWCOOKIE)
r.URL.RawQuery = q.Encode()
if access == "" {
return "", false
}
return access, true
}
func findAccessFreshCookie(w http.ResponseWriter, r *http.Request) (string, bool) {
access, err := r.Cookie(oauth2.NEWCOOKIE)
if err == http.ErrNoCookie {
return "", false
}
host := r.Host
if r.URL.Host != "" {
host = r.URL.Host
}
host = strings.Split(host, ":")[0]
hosts := strings.Split(host, ".")
if len(host) > 1 {
hosts = hosts[1:]
}
host = "." + strings.Join(hosts, ".")
setCookie(oauth2.NEWCOOKIE, "", host, w)
return access.Value, true
}
func findAccessStable(w http.ResponseWriter, r *http.Request) (string, bool) {
access, err := r.Cookie(oauth2.COOKIE)
if err == http.ErrNoCookie {
return "", false
}
return access.Value, true
}
func login(oauth2server *url.URL, scope string, w http.ResponseWriter, r *http.Request) error {
oauth2server.Path = "/users/log/" + scope
url := *r.URL
url.Host = r.Host
if url.Scheme == "" {
url.Scheme = oauth2server.Scheme
}
if url.Scheme == "" {
url.Scheme = "https"
}
q := oauth2server.Query()
q.Set(oauth2.REDIRECT, url.String())
oauth2server.RawQuery = q.Encode()
http.Redirect(w, r, oauth2server.String(), http.StatusSeeOther)
return errors.New("logging in")
}
func verify(access string, oauth2server *url.URL, scope string, w http.ResponseWriter, r *http.Request) error {
if v, ok := cache[scope]; ok && v.access == access && time.Now().Before(v.exp) {
return nil
}
oauth2server.Path = "/verify/" + scope
data := url.Values{}
data.Set("access", access)
req, err := http.NewRequest("POST", oauth2server.String(), strings.NewReader(data.Encode()))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
c := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
resp, err := c.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return login(oauth2server, scope, w, r)
}
cache[scope] = cached{
access: access,
exp: time.Now().Add(time.Minute),
}
return nil
}
func setCookie(key, value, domain string, w http.ResponseWriter) {
cookie := &http.Cookie{
Name: key,
Value: value,
Path: "/",
Domain: domain,
}
if value == "" {
cookie.Expires = time.Now().Add(-1 * time.Hour)
}
http.SetCookie(w, cookie)
}