auth

Paddy 2015-12-14 Parent:4b68bac597b7 Child:cd5f07f9811b

181:b7e685839a1b Browse Files

Break out scopes and events. This repo has gotten unwieldy, and there are portions of it that need to be imported by a large number of other packages. For example, scopes will be used in almost every API we write. Rather than importing the entirety of this codebase into every API we write, I've opted to move the scope logic out into a scopes package, with a subpackage for the defined types, which is all most projects actually want to import. We also define some event type constants, and importing those shouldn't require a project to import all our dependencies, either. So I made an events subpackage that just holds those constants. This package has become a little bit of a red-headed stepchild and is do for a refactor, but I'm trying to put that off as long as I can. The refactoring of our scopes stuff has left a bug wherein a token can be granted for scopes that don't exist. I'm going to need to revisit that, and also how to limit scopes to only be granted to the users that should be able to request them. But that's a battle for another day.

authcode.go authcode_test.go authd/server.go client.go client/login.go config.go context.go events/profile.go memstore.go oauth2.go oauth2_test.go profile.go scope.go scope_postgres.go scope_test.go session.go token.go token_test.go

     1.1 --- a/authcode.go	Sat Jul 18 03:38:27 2015 -0400
     1.2 +++ b/authcode.go	Mon Dec 14 04:17:21 2015 -0800
     1.3 @@ -6,6 +6,7 @@
     1.4  	"net/http"
     1.5  	"time"
     1.6  
     1.7 +	"code.secondbit.org/scopes.hg/types"
     1.8  	"code.secondbit.org/uuid.hg"
     1.9  )
    1.10  
    1.11 @@ -37,7 +38,7 @@
    1.12  	Created     time.Time
    1.13  	ExpiresIn   int32
    1.14  	ClientID    uuid.ID
    1.15 -	Scopes      Scopes
    1.16 +	Scopes      scopeTypes.Scopes
    1.17  	RedirectURI string
    1.18  	State       string
    1.19  	ProfileID   uuid.ID
    1.20 @@ -133,7 +134,7 @@
    1.21  	return nil
    1.22  }
    1.23  
    1.24 -func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
    1.25 +func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool) {
    1.26  	enc := json.NewEncoder(w)
    1.27  	code := r.PostFormValue("code")
    1.28  	if code == "" {
     2.1 --- a/authcode_test.go	Sat Jul 18 03:38:27 2015 -0400
     2.2 +++ b/authcode_test.go	Mon Dec 14 04:17:21 2015 -0800
     2.3 @@ -11,6 +11,7 @@
     2.4  	"testing"
     2.5  	"time"
     2.6  
     2.7 +	"code.secondbit.org/scopes.hg/types"
     2.8  	"code.secondbit.org/uuid.hg"
     2.9  )
    2.10  
    2.11 @@ -69,7 +70,7 @@
    2.12  		Created:     time.Now().Round(time.Millisecond),
    2.13  		ExpiresIn:   180,
    2.14  		ClientID:    uuid.NewID(),
    2.15 -		Scopes:      stringsToScopes([]string{"scope"}),
    2.16 +		Scopes:      scopeTypes.StringsToScopes([]string{"scope"}),
    2.17  		RedirectURI: "redirectURI",
    2.18  		State:       "state",
    2.19  	}
    2.20 @@ -161,7 +162,7 @@
    2.21  		Created:     time.Now().Round(time.Millisecond),
    2.22  		ExpiresIn:   180,
    2.23  		ClientID:    uuid.NewID(),
    2.24 -		Scopes:      stringsToScopes([]string{"scope"}),
    2.25 +		Scopes:      scopeTypes.StringsToScopes([]string{"scope"}),
    2.26  		RedirectURI: "redirectURI",
    2.27  		State:       "state",
    2.28  	}
    2.29 @@ -343,7 +344,7 @@
    2.30  		Created:     time.Now().Round(time.Millisecond),
    2.31  		ExpiresIn:   180,
    2.32  		ClientID:    uuid.NewID(),
    2.33 -		Scopes:      stringsToScopes([]string{"scope"}),
    2.34 +		Scopes:      scopeTypes.StringsToScopes([]string{"scope"}),
    2.35  		RedirectURI: "redirectURI",
    2.36  		State:       "state",
    2.37  	}
     3.1 --- a/authd/server.go	Sat Jul 18 03:38:27 2015 -0400
     3.2 +++ b/authd/server.go	Mon Dec 14 04:17:21 2015 -0800
     3.3 @@ -41,7 +41,6 @@
     3.4  		config.ProfileStore = &p
     3.5  		config.TokenStore = &p
     3.6  		config.SessionStore = &p
     3.7 -		config.ScopeStore = &p
     3.8  	} else {
     3.9  		store := auth.NewMemstore()
    3.10  		config.ClientStore = store
    3.11 @@ -49,7 +48,6 @@
    3.12  		config.ProfileStore = store
    3.13  		config.TokenStore = store
    3.14  		config.SessionStore = store
    3.15 -		config.ScopeStore = store
    3.16  	}
    3.17  	config.Template = template.Must(template.New("base").ParseGlob("./templates/*.gotmpl"))
    3.18  	config.LoginURI = "/login"
    3.19 @@ -70,13 +68,6 @@
    3.20  	if err != nil {
    3.21  		panic(err)
    3.22  	}
    3.23 -	err = context.CreateScopes([]auth.Scope{
    3.24 -		auth.ScopeLoginAdmin,
    3.25 -		{ID: "subscriptions", Name: "Manage subscriptions", Description: "Create, view, edit, and cancel your subscriptions."},
    3.26 -	})
    3.27 -	if err != nil && err != auth.ErrScopeAlreadyExists {
    3.28 -		log.Fatal(err)
    3.29 -	}
    3.30  
    3.31  	router := mux.NewRouter()
    3.32  	auth.RegisterOAuth2(router, context)
     4.1 --- a/client.go	Sat Jul 18 03:38:27 2015 -0400
     4.2 +++ b/client.go	Mon Dec 14 04:17:21 2015 -0800
     4.3 @@ -15,6 +15,7 @@
     4.4  	"github.com/PuerkitoBio/purell"
     4.5  	"github.com/gorilla/mux"
     4.6  
     4.7 +	"code.secondbit.org/scopes.hg/types"
     4.8  	"code.secondbit.org/uuid.hg"
     4.9  )
    4.10  
    4.11 @@ -1074,8 +1075,8 @@
    4.12  	encode(w, r, http.StatusCreated, resp)
    4.13  }
    4.14  
    4.15 -func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
    4.16 -	scopes = stringsToScopes(strings.Split(r.PostFormValue("scope"), " "))
    4.17 +func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool) {
    4.18 +	scopes = scopeTypes.StringsToScopes(strings.Split(r.PostFormValue("scope"), " "))
    4.19  	valid = true
    4.20  	return
    4.21  }
     5.1 --- a/client/login.go	Sat Jul 18 03:38:27 2015 -0400
     5.2 +++ b/client/login.go	Mon Dec 14 04:17:21 2015 -0800
     5.3 @@ -2,10 +2,11 @@
     5.4  
     5.5  import (
     5.6  	"code.secondbit.org/auth.hg"
     5.7 +	"code.secondbit.org/scopes.hg/types"
     5.8  )
     5.9  
    5.10  func (c *Client) GetLogin(value string) (auth.Login, error) {
    5.11 -	resp, err := c.Get("/logins/"+value, auth.Scopes{auth.ScopeLoginAdmin}.Strings(), nil)
    5.12 +	resp, err := c.Get("/logins/"+value, scopeTypes.Scopes{auth.ScopeLoginAdmin}.Strings(), nil)
    5.13  	if err != nil {
    5.14  		hErr, ok := err.(httpErrors)
    5.15  		if ok && hErr[0].Slug == auth.RequestErrNotFound {
     6.1 --- a/config.go	Sat Jul 18 03:38:27 2015 -0400
     6.2 +++ b/config.go	Mon Dec 14 04:17:21 2015 -0800
     6.3 @@ -26,7 +26,6 @@
     6.4  	ProfileStore    profileStore
     6.5  	TokenStore      tokenStore
     6.6  	SessionStore    sessionStore
     6.7 -	ScopeStore      scopeStore
     6.8  	EventsPublisher events.Publisher
     6.9  	Template        *template.Template
    6.10  	LoginURI        string
     7.1 --- a/context.go	Sat Jul 18 03:38:27 2015 -0400
     7.2 +++ b/context.go	Mon Dec 14 04:17:21 2015 -0800
     7.3 @@ -22,7 +22,6 @@
     7.4  	profiles        profileStore
     7.5  	tokens          tokenStore
     7.6  	sessions        sessionStore
     7.7 -	scopes          scopeStore
     7.8  	eventsPublisher events.Publisher
     7.9  	config          Config
    7.10  }
    7.11 @@ -39,7 +38,6 @@
    7.12  		profiles:        config.ProfileStore,
    7.13  		tokens:          config.TokenStore,
    7.14  		sessions:        config.SessionStore,
    7.15 -		scopes:          config.ScopeStore,
    7.16  		eventsPublisher: config.EventsPublisher,
    7.17  		template:        config.Template,
    7.18  		config:          config,
    7.19 @@ -457,41 +455,6 @@
    7.20  	return c.sessions.listSessions(profile, before, num)
    7.21  }
    7.22  
    7.23 -func (c Context) CreateScopes(scopes []Scope) error {
    7.24 -	if c.scopes == nil {
    7.25 -		return ErrNoScopeStore
    7.26 -	}
    7.27 -	return c.scopes.createScopes(scopes)
    7.28 -}
    7.29 -
    7.30 -func (c Context) GetScopes(ids []string) ([]Scope, error) {
    7.31 -	if c.scopes == nil {
    7.32 -		return []Scope{}, ErrNoScopeStore
    7.33 -	}
    7.34 -	return c.scopes.getScopes(ids)
    7.35 -}
    7.36 -
    7.37 -func (c Context) UpdateScope(id string, change ScopeChange) error {
    7.38 -	if c.scopes == nil {
    7.39 -		return ErrNoScopeStore
    7.40 -	}
    7.41 -	return c.scopes.updateScope(id, change)
    7.42 -}
    7.43 -
    7.44 -func (c Context) RemoveScopes(ids []string) error {
    7.45 -	if c.scopes == nil {
    7.46 -		return ErrNoScopeStore
    7.47 -	}
    7.48 -	return c.scopes.removeScopes(ids)
    7.49 -}
    7.50 -
    7.51 -func (c Context) ListScopes() ([]Scope, error) {
    7.52 -	if c.scopes == nil {
    7.53 -		return []Scope{}, ErrNoScopeStore
    7.54 -	}
    7.55 -	return c.scopes.listScopes()
    7.56 -}
    7.57 -
    7.58  func (c Context) SendModelEvent(m events.Model, action string) error {
    7.59  	if c.eventsPublisher == nil {
    7.60  		log.Println("Event publisher not set!")
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/events/profile.go	Mon Dec 14 04:17:21 2015 -0800
     8.3 @@ -0,0 +1,6 @@
     8.4 +package authEvents
     8.5 +
     8.6 +const (
     8.7 +	ActionResendVerification = "resend_verification"
     8.8 +	ActionLoginVerified      = "login_verified"
     8.9 +)
     9.1 --- a/memstore.go	Sat Jul 18 03:38:27 2015 -0400
     9.2 +++ b/memstore.go	Mon Dec 14 04:17:21 2015 -0800
     9.3 @@ -31,9 +31,6 @@
     9.4  
     9.5  	sessions    map[string]Session
     9.6  	sessionLock sync.RWMutex
     9.7 -
     9.8 -	scopes    map[string]Scope
     9.9 -	scopeLock sync.RWMutex
    9.10  }
    9.11  
    9.12  // NewMemstore returns an in-memory version of our datastores,
    9.13 @@ -53,7 +50,6 @@
    9.14  		logins:              map[string]Login{},
    9.15  		profileLoginLookup:  map[string][]string{},
    9.16  		sessions:            map[string]Session{},
    9.17 -		scopes:              map[string]Scope{},
    9.18  	}
    9.19  }
    9.20  
    10.1 --- a/oauth2.go	Sat Jul 18 03:38:27 2015 -0400
    10.2 +++ b/oauth2.go	Mon Dec 14 04:17:21 2015 -0800
    10.3 @@ -15,6 +15,7 @@
    10.4  	"sync"
    10.5  	"time"
    10.6  
    10.7 +	"code.secondbit.org/scopes.hg/types"
    10.8  	"code.secondbit.org/uuid.hg"
    10.9  	"github.com/gorilla/mux"
   10.10  )
   10.11 @@ -68,7 +69,7 @@
   10.12  // The ReturnToken will be called when a token is created and needs to be returned to the client. If it returns true, the token
   10.13  // was successfully returned and the Invalidate function will be called asynchronously.
   10.14  type GrantType struct {
   10.15 -	Validate      func(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool)
   10.16 +	Validate      func(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool)
   10.17  	Invalidate    func(r *http.Request, context Context) error
   10.18  	ReturnToken   func(w http.ResponseWriter, r *http.Request, token Token, context Context) bool
   10.19  	AuditString   func(r *http.Request) string
   10.20 @@ -278,21 +279,20 @@
   10.21  		http.Redirect(w, r, redirectURL.String(), http.StatusFound)
   10.22  		return
   10.23  	}
   10.24 -	scopeParams := strings.Split(r.URL.Query().Get("scope"), " ")
   10.25 -	scopes, err := context.GetScopes(scopeParams)
   10.26 -	if err != nil {
   10.27 -		if err == ErrScopeNotFound {
   10.28 -			q.Add("error", "invalid_scope")
   10.29 -			redirectURL.RawQuery = q.Encode()
   10.30 -			http.Redirect(w, r, redirectURL.String(), http.StatusFound)
   10.31 -			return
   10.32 -		}
   10.33 -		log.Println("Error retrieving scopes:", err)
   10.34 -		q.Add("error", "server_error")
   10.35 +	scopes := scopeTypes.StringsToScopes(strings.Split(r.URL.Query().Get("scope"), " "))
   10.36 +	// BUG(paddy): need to check if Scopes actually exist
   10.37 +	/*if err == ErrScopeNotFound {
   10.38 +		q.Add("error", "invalid_scope")
   10.39  		redirectURL.RawQuery = q.Encode()
   10.40  		http.Redirect(w, r, redirectURL.String(), http.StatusFound)
   10.41  		return
   10.42  	}
   10.43 +	log.Println("Error retrieving scopes:", err)
   10.44 +	q.Add("error", "server_error")
   10.45 +	redirectURL.RawQuery = q.Encode()
   10.46 +	http.Redirect(w, r, redirectURL.String(), http.StatusFound)
   10.47 +	return
   10.48 +	*/
   10.49  	if r.Method == "POST" {
   10.50  		if checkCSRF(r, session) != nil {
   10.51  			log.Println("CSRF attempt detected.")
    11.1 --- a/oauth2_test.go	Sat Jul 18 03:38:27 2015 -0400
    11.2 +++ b/oauth2_test.go	Mon Dec 14 04:17:21 2015 -0800
    11.3 @@ -11,6 +11,7 @@
    11.4  	"testing"
    11.5  	"time"
    11.6  
    11.7 +	"code.secondbit.org/scopes.hg/types"
    11.8  	"code.secondbit.org/uuid.hg"
    11.9  )
   11.10  
   11.11 @@ -35,7 +36,6 @@
   11.12  		profiles:  store,
   11.13  		tokens:    store,
   11.14  		sessions:  store,
   11.15 -		scopes:    store,
   11.16  	}
   11.17  	client := Client{
   11.18  		ID:      uuid.NewID(),
   11.19 @@ -78,15 +78,17 @@
   11.20  	if err != nil {
   11.21  		t.Fatal("Can't store session:", err)
   11.22  	}
   11.23 -	scope := Scope{
   11.24 -		ID:          "testscope",
   11.25 -		Name:        "Test Scope",
   11.26 -		Description: "Hug dispensation.",
   11.27 -	}
   11.28 -	err = testContext.CreateScopes([]Scope{scope})
   11.29 -	if err != nil {
   11.30 -		t.Fatal("Can't store scope:", err)
   11.31 -	}
   11.32 +	/*
   11.33 +		scope := scopeTypes.Scope{
   11.34 +			ID:          "testscope",
   11.35 +			Name:        "Test Scope",
   11.36 +			Description: "Hug dispensation.",
   11.37 +		}
   11.38 +		err = testContext.CreateScopes([]Scope{scope})
   11.39 +		if err != nil {
   11.40 +			t.Fatal("Can't store scope:", err)
   11.41 +		}*/
   11.42 +	// BUG(paddy): create the scopes in the scopeStore
   11.43  	req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
   11.44  	if err != nil {
   11.45  		t.Fatal("Can't build request:", err)
   11.46 @@ -457,7 +459,6 @@
   11.47  		profiles:  store,
   11.48  		tokens:    store,
   11.49  		sessions:  store,
   11.50 -		scopes:    store,
   11.51  	}
   11.52  	client := Client{
   11.53  		ID:      uuid.NewID(),
   11.54 @@ -492,15 +493,18 @@
   11.55  	if err != nil {
   11.56  		t.Fatal("Can't store session:", err)
   11.57  	}
   11.58 -	scope := Scope{
   11.59 -		ID:          "testscope",
   11.60 -		Name:        "Test Scope",
   11.61 -		Description: "High five fabrication.",
   11.62 -	}
   11.63 -	err = testContext.CreateScopes([]Scope{scope})
   11.64 -	if err != nil {
   11.65 -		t.Fatal("Can't create scope:", err)
   11.66 -	}
   11.67 +	/*
   11.68 +		scope := scopeTypes.Scope{
   11.69 +			ID:          "testscope",
   11.70 +			Name:        "Test Scope",
   11.71 +			Description: "High five fabrication.",
   11.72 +		}
   11.73 +		// BUG(paddy): Create the Scopes in the scopeStore
   11.74 +			err = testContext.CreateScopes([]Scope{scope})
   11.75 +			if err != nil {
   11.76 +				t.Fatal("Can't create scope:", err)
   11.77 +			}
   11.78 +	*/
   11.79  	req, err := http.NewRequest("GET", "https://test.auth.secondbit.org/oauth2/grant", nil)
   11.80  	if err != nil {
   11.81  		t.Fatal("Can't build request:", err)
   11.82 @@ -794,7 +798,7 @@
   11.83  		Created:     time.Now().Round(time.Millisecond),
   11.84  		ExpiresIn:   600,
   11.85  		ClientID:    client.ID,
   11.86 -		Scopes:      stringsToScopes([]string{"testscope"}),
   11.87 +		Scopes:      scopeTypes.StringsToScopes([]string{"testscope"}),
   11.88  		RedirectURI: "https://client.secondbit.org/",
   11.89  		State:       "teststate",
   11.90  		ProfileID:   uuid.NewID(),
    12.1 --- a/profile.go	Sat Jul 18 03:38:27 2015 -0400
    12.2 +++ b/profile.go	Mon Dec 14 04:17:21 2015 -0800
    12.3 @@ -9,7 +9,9 @@
    12.4  	"strings"
    12.5  	"time"
    12.6  
    12.7 +	"code.secondbit.org/auth.hg/events"
    12.8  	"code.secondbit.org/events.hg"
    12.9 +	"code.secondbit.org/scopes.hg/types"
   12.10  	"code.secondbit.org/uuid.hg"
   12.11  
   12.12  	"github.com/gorilla/mux"
   12.13 @@ -26,10 +28,6 @@
   12.14  	MaxNameLength = 64
   12.15  	// MaxEmailLength is the maximum length, in bytes, of an email address, exclusive.
   12.16  	MaxEmailLength = 64
   12.17 -
   12.18 -	// ActionResendVerification is the action property of the event sent when the user wants to resend their login verification code.
   12.19 -	ActionResendVerification = "resend_verification"
   12.20 -	ActionLoginVerified      = "login_verified"
   12.21  )
   12.22  
   12.23  var (
   12.24 @@ -71,7 +69,7 @@
   12.25  	// duration, to prevent brute force attacks.
   12.26  	ErrProfileLocked = errors.New("profile locked")
   12.27  
   12.28 -	ScopeLoginAdmin = Scope{ID: "login_admin", Name: "Administer Logins", Description: "Read and write logins, bypassing ACL."}
   12.29 +	ScopeLoginAdmin = scopeTypes.Scope{ID: "login_admin", Name: "Administer Logins", Description: "Read and write logins, bypassing ACL."}
   12.30  )
   12.31  
   12.32  // Profile represents a single user of the service,
   12.33 @@ -900,7 +898,7 @@
   12.34  			encode(w, r, http.StatusInternalServerError, actOfGodResponse)
   12.35  			return
   12.36  		}
   12.37 -		go context.SendModelEvent(login, ActionLoginVerified)
   12.38 +		go context.SendModelEvent(login, authEvents.ActionLoginVerified)
   12.39  		login.Verified = true
   12.40  	} else if req.ResendVerification != nil {
   12.41  		if !*req.ResendVerification {
   12.42 @@ -908,7 +906,7 @@
   12.43  			encode(w, r, http.StatusBadRequest, Response{Errors: errors})
   12.44  			return
   12.45  		}
   12.46 -		go context.SendModelEvent(login, ActionResendVerification)
   12.47 +		go context.SendModelEvent(login, authEvents.ActionResendVerification)
   12.48  	} else {
   12.49  		errors = append(errors, RequestError{Slug: RequestErrMissing, Field: "/"})
   12.50  		encode(w, r, http.StatusBadRequest, Response{Errors: errors})
    13.1 --- a/scope.go	Sat Jul 18 03:38:27 2015 -0400
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,151 +0,0 @@
    13.4 -package auth
    13.5 -
    13.6 -import (
    13.7 -	"errors"
    13.8 -	"sort"
    13.9 -)
   13.10 -
   13.11 -var (
   13.12 -	ErrNoScopeStore       = errors.New("scopeStore not set in Context")
   13.13 -	ErrScopeNotFound      = errors.New("scope not found")
   13.14 -	ErrScopeAlreadyExists = errors.New("scope already exists")
   13.15 -)
   13.16 -
   13.17 -// Scope represents a limit on the access that a grant provides.
   13.18 -type Scope struct {
   13.19 -	ID          string
   13.20 -	Name        string
   13.21 -	Description string
   13.22 -}
   13.23 -
   13.24 -func (s *Scope) ApplyChange(change ScopeChange) {
   13.25 -	if change.Name != nil {
   13.26 -		s.Name = *change.Name
   13.27 -	}
   13.28 -	if change.Description != nil {
   13.29 -		s.Description = *change.Description
   13.30 -	}
   13.31 -}
   13.32 -
   13.33 -type Scopes []Scope
   13.34 -
   13.35 -func (s Scopes) Len() int {
   13.36 -	return len(s)
   13.37 -}
   13.38 -
   13.39 -func (s Scopes) Swap(i, j int) {
   13.40 -	s[i], s[j] = s[j], s[i]
   13.41 -}
   13.42 -
   13.43 -func (s Scopes) Less(i, j int) bool {
   13.44 -	return s[i].ID < s[j].ID
   13.45 -}
   13.46 -
   13.47 -func (s Scopes) Strings() []string {
   13.48 -	res := make([]string, len(s))
   13.49 -	for pos, scope := range s {
   13.50 -		res[pos] = scope.ID
   13.51 -	}
   13.52 -	return res
   13.53 -}
   13.54 -
   13.55 -func stringsToScopes(s []string) Scopes {
   13.56 -	res := make(Scopes, len(s))
   13.57 -	for pos, scope := range s {
   13.58 -		res[pos] = Scope{ID: scope}
   13.59 -	}
   13.60 -	return res
   13.61 -}
   13.62 -
   13.63 -// ScopeChange represents a change to a Scope.
   13.64 -type ScopeChange struct {
   13.65 -	Name        *string
   13.66 -	Description *string
   13.67 -}
   13.68 -
   13.69 -func (s ScopeChange) Empty() bool {
   13.70 -	return s.Name == nil && s.Description == nil
   13.71 -}
   13.72 -
   13.73 -type scopeStore interface {
   13.74 -	createScopes(scopes []Scope) error
   13.75 -	getScopes(ids []string) ([]Scope, error)
   13.76 -	updateScope(id string, change ScopeChange) error
   13.77 -	removeScopes(ids []string) error
   13.78 -	listScopes() ([]Scope, error)
   13.79 -}
   13.80 -
   13.81 -func (m *memstore) createScopes(scopes []Scope) error {
   13.82 -	m.scopeLock.Lock()
   13.83 -	defer m.scopeLock.Unlock()
   13.84 -
   13.85 -	for _, scope := range scopes {
   13.86 -		if _, ok := m.scopes[scope.ID]; ok {
   13.87 -			return ErrScopeAlreadyExists
   13.88 -		}
   13.89 -	}
   13.90 -	for _, scope := range scopes {
   13.91 -		m.scopes[scope.ID] = scope
   13.92 -	}
   13.93 -	return nil
   13.94 -}
   13.95 -
   13.96 -func (m *memstore) getScopes(ids []string) ([]Scope, error) {
   13.97 -	m.scopeLock.RLock()
   13.98 -	defer m.scopeLock.RUnlock()
   13.99 -
  13.100 -	scopes := []Scope{}
  13.101 -	for _, id := range ids {
  13.102 -		scope, ok := m.scopes[id]
  13.103 -		if !ok {
  13.104 -			continue
  13.105 -		}
  13.106 -		scopes = append(scopes, scope)
  13.107 -	}
  13.108 -	sorted := Scopes(scopes)
  13.109 -	sort.Sort(sorted)
  13.110 -	scopes = sorted
  13.111 -	return scopes, nil
  13.112 -}
  13.113 -
  13.114 -func (m *memstore) updateScope(id string, change ScopeChange) error {
  13.115 -	m.scopeLock.Lock()
  13.116 -	defer m.scopeLock.Unlock()
  13.117 -
  13.118 -	scope, ok := m.scopes[id]
  13.119 -	if !ok {
  13.120 -		return ErrScopeNotFound
  13.121 -	}
  13.122 -	scope.ApplyChange(change)
  13.123 -	m.scopes[id] = scope
  13.124 -	return nil
  13.125 -}
  13.126 -
  13.127 -func (m *memstore) removeScopes(ids []string) error {
  13.128 -	m.scopeLock.Lock()
  13.129 -	defer m.scopeLock.Unlock()
  13.130 -
  13.131 -	for _, id := range ids {
  13.132 -		if _, ok := m.scopes[id]; !ok {
  13.133 -			return ErrScopeNotFound
  13.134 -		}
  13.135 -	}
  13.136 -	for _, id := range ids {
  13.137 -		delete(m.scopes, id)
  13.138 -	}
  13.139 -	return nil
  13.140 -}
  13.141 -
  13.142 -func (m *memstore) listScopes() ([]Scope, error) {
  13.143 -	m.scopeLock.RLock()
  13.144 -	defer m.scopeLock.RUnlock()
  13.145 -
  13.146 -	scopes := []Scope{}
  13.147 -	for _, scope := range m.scopes {
  13.148 -		scopes = append(scopes, scope)
  13.149 -	}
  13.150 -	sorted := Scopes(scopes)
  13.151 -	sort.Sort(sorted)
  13.152 -	scopes = sorted
  13.153 -	return scopes, nil
  13.154 -}
    14.1 --- a/scope_postgres.go	Sat Jul 18 03:38:27 2015 -0400
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,180 +0,0 @@
    14.4 -package auth
    14.5 -
    14.6 -import (
    14.7 -	"code.secondbit.org/pqarrays.hg"
    14.8 -	"database/sql/driver"
    14.9 -	"github.com/lib/pq"
   14.10 -	"github.com/secondbit/pan"
   14.11 -)
   14.12 -
   14.13 -func (s Scope) GetSQLTableName() string {
   14.14 -	return "scopes"
   14.15 -}
   14.16 -
   14.17 -func (s Scopes) Value() (driver.Value, error) {
   14.18 -	ids := make(pqarrays.StringArray, 0, len(s))
   14.19 -	for _, scope := range s {
   14.20 -		ids = append(ids, scope.ID)
   14.21 -	}
   14.22 -	return ids.Value()
   14.23 -}
   14.24 -
   14.25 -func (s *Scopes) Scan(value interface{}) error {
   14.26 -	*s = (*s)[:0]
   14.27 -	var ids pqarrays.StringArray
   14.28 -	err := ids.Scan(value)
   14.29 -	if err != nil {
   14.30 -		return err
   14.31 -	}
   14.32 -	for _, id := range ids {
   14.33 -		*s = append(*s, Scope{ID: id})
   14.34 -	}
   14.35 -	return nil
   14.36 -}
   14.37 -
   14.38 -func (p *postgres) createScopesSQL(scopes []Scope) *pan.Query {
   14.39 -	fields, _ := pan.GetFields(scopes[0])
   14.40 -	query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(scopes[0]))
   14.41 -	query.Include("(" + pan.QueryList(fields) + ")")
   14.42 -	query.Include("VALUES")
   14.43 -	query.FlushExpressions(" ")
   14.44 -	for _, scope := range scopes {
   14.45 -		_, values := pan.GetFields(scope)
   14.46 -		query.Include("("+pan.VariableList(len(values))+")", values...)
   14.47 -	}
   14.48 -	return query.FlushExpressions(", ")
   14.49 -}
   14.50 -
   14.51 -func (p *postgres) createScopes(scopes []Scope) error {
   14.52 -	if len(scopes) < 1 {
   14.53 -		return nil
   14.54 -	}
   14.55 -	query := p.createScopesSQL(scopes)
   14.56 -	_, err := p.db.Exec(query.String(), query.Args...)
   14.57 -	if e, ok := err.(*pq.Error); ok && e.Constraint == "scopes_pkey" {
   14.58 -		err = ErrScopeAlreadyExists
   14.59 -	}
   14.60 -	return err
   14.61 -}
   14.62 -
   14.63 -func (p *postgres) getScopesSQL(ids []string) *pan.Query {
   14.64 -	var scope Scope
   14.65 -	intids := make([]interface{}, len(ids))
   14.66 -	for pos, id := range ids {
   14.67 -		intids[pos] = id
   14.68 -	}
   14.69 -	fields, _ := pan.GetFields(scope)
   14.70 -	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
   14.71 -	query.IncludeWhere()
   14.72 -	query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
   14.73 -	query.Include("("+pan.VariableList(len(ids))+")", intids...)
   14.74 -	return query.FlushExpressions(" ")
   14.75 -}
   14.76 -
   14.77 -func (p *postgres) getScopes(ids []string) ([]Scope, error) {
   14.78 -	query := p.getScopesSQL(ids)
   14.79 -	rows, err := p.db.Query(query.String(), query.Args...)
   14.80 -	if err != nil {
   14.81 -		return []Scope{}, err
   14.82 -	}
   14.83 -	var scopes []Scope
   14.84 -	for rows.Next() {
   14.85 -		var scope Scope
   14.86 -		err := pan.Unmarshal(rows, &scope)
   14.87 -		if err != nil {
   14.88 -			return scopes, err
   14.89 -		}
   14.90 -		scopes = append(scopes, scope)
   14.91 -	}
   14.92 -	if err = rows.Err(); err != nil {
   14.93 -		return scopes, err
   14.94 -	}
   14.95 -	return scopes, nil
   14.96 -}
   14.97 -
   14.98 -func (p *postgres) updateScopeSQL(id string, change ScopeChange) *pan.Query {
   14.99 -	var scope Scope
  14.100 -	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(scope)+" SET ")
  14.101 -	query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Name")+" = ?", change.Name)
  14.102 -	query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Description")+" = ?", change.Description)
  14.103 -	query.FlushExpressions(", ")
  14.104 -	query.IncludeWhere()
  14.105 -	query.Include(pan.GetUnquotedColumn(scope, "ID")+" = ?", id)
  14.106 -	return query.FlushExpressions(" ")
  14.107 -}
  14.108 -
  14.109 -func (p *postgres) updateScope(id string, change ScopeChange) error {
  14.110 -	if change.Empty() {
  14.111 -		return nil
  14.112 -	}
  14.113 -	query := p.updateScopeSQL(id, change)
  14.114 -	res, err := p.db.Exec(query.String(), query.Args...)
  14.115 -	if err != nil {
  14.116 -		return err
  14.117 -	}
  14.118 -	rows, err := res.RowsAffected()
  14.119 -	if err != nil {
  14.120 -		return err
  14.121 -	}
  14.122 -	if rows < 1 {
  14.123 -		return ErrScopeNotFound
  14.124 -	}
  14.125 -	return err
  14.126 -}
  14.127 -
  14.128 -func (p *postgres) removeScopesSQL(ids []string) *pan.Query {
  14.129 -	var scope Scope
  14.130 -	intids := make([]interface{}, len(ids))
  14.131 -	for pos, id := range ids {
  14.132 -		intids[pos] = id
  14.133 -	}
  14.134 -	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(scope))
  14.135 -	query.IncludeWhere()
  14.136 -	query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
  14.137 -	query.Include("("+pan.VariableList(len(ids))+")", intids...)
  14.138 -	return query.FlushExpressions(" ")
  14.139 -}
  14.140 -
  14.141 -func (p *postgres) removeScopes(ids []string) error {
  14.142 -	query := p.removeScopesSQL(ids)
  14.143 -	res, err := p.db.Exec(query.String(), query.Args...)
  14.144 -	if err != nil {
  14.145 -		return err
  14.146 -	}
  14.147 -	rows, err := res.RowsAffected()
  14.148 -	if err != nil {
  14.149 -		return err
  14.150 -	}
  14.151 -	if rows < 1 {
  14.152 -		return ErrScopeNotFound
  14.153 -	}
  14.154 -	return nil
  14.155 -}
  14.156 -
  14.157 -func (p *postgres) listScopesSQL() *pan.Query {
  14.158 -	var scope Scope
  14.159 -	fields, _ := pan.GetFields(scope)
  14.160 -	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
  14.161 -	return query.FlushExpressions(" ")
  14.162 -}
  14.163 -
  14.164 -func (p *postgres) listScopes() ([]Scope, error) {
  14.165 -	query := p.listScopesSQL()
  14.166 -	rows, err := p.db.Query(query.String(), query.Args...)
  14.167 -	if err != nil {
  14.168 -		return []Scope{}, err
  14.169 -	}
  14.170 -	var scopes []Scope
  14.171 -	for rows.Next() {
  14.172 -		var scope Scope
  14.173 -		err = pan.Unmarshal(rows, &scope)
  14.174 -		if err != nil {
  14.175 -			return scopes, err
  14.176 -		}
  14.177 -		scopes = append(scopes, scope)
  14.178 -	}
  14.179 -	if err = rows.Err(); err != nil {
  14.180 -		return scopes, err
  14.181 -	}
  14.182 -	return scopes, nil
  14.183 -}
    15.1 --- a/scope_test.go	Sat Jul 18 03:38:27 2015 -0400
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,157 +0,0 @@
    15.4 -package auth
    15.5 -
    15.6 -import "os"
    15.7 -import "testing"
    15.8 -
    15.9 -func init() {
   15.10 -	if os.Getenv("PG_TEST_DB") != "" {
   15.11 -		p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
   15.12 -		if err != nil {
   15.13 -			panic(err)
   15.14 -		}
   15.15 -		scopeStores = append(scopeStores, &p)
   15.16 -	}
   15.17 -}
   15.18 -
   15.19 -var scopeStores = []scopeStore{NewMemstore()}
   15.20 -
   15.21 -func compareScopes(scope1, scope2 Scope) (success bool, field string, val1, val2 interface{}) {
   15.22 -	if scope1.ID != scope2.ID {
   15.23 -		return false, "ID", scope1.ID, scope2.ID
   15.24 -	}
   15.25 -	if scope1.Name != scope2.Name {
   15.26 -		return false, "Name", scope1.Name, scope2.Name
   15.27 -	}
   15.28 -	if scope1.Description != scope2.Description {
   15.29 -		return false, "Description", scope1.Description, scope2.Description
   15.30 -	}
   15.31 -	return true, "", nil, nil
   15.32 -}
   15.33 -
   15.34 -func TestScopeInScopeStore(t *testing.T) {
   15.35 -	scope := Scope{
   15.36 -		ID:          "testscope",
   15.37 -		Name:        "Test Scope",
   15.38 -		Description: "Access to testing data.",
   15.39 -	}
   15.40 -	scope2 := Scope{
   15.41 -		ID:          "testscope2",
   15.42 -		Name:        "Test Scope 2",
   15.43 -		Description: "Access to minions.",
   15.44 -	}
   15.45 -	scope3 := Scope{
   15.46 -		ID:          "testscope3",
   15.47 -		Name:        "Test Scope 3",
   15.48 -		Description: "Access to bananas.",
   15.49 -	}
   15.50 -	updatedName := "Updated Scope"
   15.51 -	updatedDescription := "An updated scope."
   15.52 -	update := ScopeChange{
   15.53 -		Name: &updatedName,
   15.54 -	}
   15.55 -	update2 := ScopeChange{
   15.56 -		Description: &updatedDescription,
   15.57 -	}
   15.58 -	update3 := ScopeChange{
   15.59 -		Name:        &updatedName,
   15.60 -		Description: &updatedDescription,
   15.61 -	}
   15.62 -	for _, store := range scopeStores {
   15.63 -		context := Context{scopes: store}
   15.64 -		retrieved, err := context.GetScopes([]string{scope.ID})
   15.65 -		if len(retrieved) != 0 {
   15.66 -			t.Logf("%+v", retrieved)
   15.67 -			t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
   15.68 -		}
   15.69 -		err = context.CreateScopes([]Scope{scope})
   15.70 -		if err != nil {
   15.71 -			t.Errorf("Error saving scope to %T: %s", store, err)
   15.72 -		}
   15.73 -		err = context.CreateScopes([]Scope{scope})
   15.74 -		if err != ErrScopeAlreadyExists {
   15.75 -			t.Errorf("Expected ErrScopeAlreadyExists, got %s instead for %T", err, store)
   15.76 -		}
   15.77 -		retrieved, err = context.GetScopes([]string{scope.ID})
   15.78 -		if err != nil {
   15.79 -			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
   15.80 -		}
   15.81 -		if len(retrieved) != 1 {
   15.82 -			t.Logf("%+v", retrieved)
   15.83 -			t.Errorf("Expected %d results, got %d from %T", 1, len(retrieved), store)
   15.84 -		}
   15.85 -		success, field, val1, val2 := compareScopes(scope, retrieved[0])
   15.86 -		if !success {
   15.87 -			t.Errorf("Expected %s to be %+v, got %+v from %T", field, val1, val2, store)
   15.88 -		}
   15.89 -		err = context.CreateScopes([]Scope{scope2, scope3})
   15.90 -		if err != nil {
   15.91 -			t.Errorf("Unexpected error trying to create scope2 and scope3 in %T: %+v", store, err)
   15.92 -		}
   15.93 -		retrieved, err = context.GetScopes([]string{scope.ID, scope2.ID, scope3.ID})
   15.94 -		if err != nil {
   15.95 -			t.Errorf("Unexpected error retrieving scopes from  %T: %+v", store, err)
   15.96 -		}
   15.97 -		if len(retrieved) != 3 {
   15.98 -			t.Logf("%+v", retrieved)
   15.99 -			t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
  15.100 -		}
  15.101 -		for pos, s := range []Scope{scope, scope2, scope3} {
  15.102 -			success, field, val1, val2 = compareScopes(s, retrieved[pos])
  15.103 -			if !success {
  15.104 -				t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
  15.105 -			}
  15.106 -		}
  15.107 -		err = context.UpdateScope(scope.ID, update)
  15.108 -		if err != nil {
  15.109 -			t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
  15.110 -		}
  15.111 -		scope.ApplyChange(update)
  15.112 -		err = context.UpdateScope(scope2.ID, update2)
  15.113 -		if err != nil {
  15.114 -			t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
  15.115 -		}
  15.116 -		scope2.ApplyChange(update2)
  15.117 -		err = context.UpdateScope(scope3.ID, update3)
  15.118 -		if err != nil {
  15.119 -			t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
  15.120 -		}
  15.121 -		scope3.ApplyChange(update3)
  15.122 -		retrieved, err = context.ListScopes()
  15.123 -		if err != nil {
  15.124 -			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
  15.125 -		}
  15.126 -		if len(retrieved) != 3 {
  15.127 -			t.Logf("%+v", retrieved)
  15.128 -			t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
  15.129 -		}
  15.130 -		for pos, s := range []Scope{scope, scope2, scope3} {
  15.131 -			success, field, val1, val2 = compareScopes(s, retrieved[pos])
  15.132 -			if !success {
  15.133 -				t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
  15.134 -			}
  15.135 -		}
  15.136 -		err = context.RemoveScopes([]string{scope.ID, scope2.ID, scope3.ID})
  15.137 -		if err != nil {
  15.138 -			t.Errorf("Unexpected error removing scopes from %T: %+v", store, err)
  15.139 -		}
  15.140 -		retrieved, err = context.ListScopes()
  15.141 -		if err != nil {
  15.142 -			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
  15.143 -		}
  15.144 -		if len(retrieved) != 0 {
  15.145 -			t.Logf("%+v", retrieved)
  15.146 -			t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
  15.147 -		}
  15.148 -		err = context.RemoveScopes([]string{scope.ID})
  15.149 -		if err == nil {
  15.150 -			t.Errorf("No error returned removing non-existent scopes from %T", store)
  15.151 -		}
  15.152 -		if err != ErrScopeNotFound {
  15.153 -			t.Errorf("Unexpected error removing non-existent scopes from %T: %+v", store, err)
  15.154 -		}
  15.155 -		err = context.UpdateScope(scope.ID, update)
  15.156 -		if err != ErrScopeNotFound {
  15.157 -			t.Errorf("Unexpected error updating non-existent scopes from %T: %+v", store, err)
  15.158 -		}
  15.159 -	}
  15.160 -}
    16.1 --- a/session.go	Sat Jul 18 03:38:27 2015 -0400
    16.2 +++ b/session.go	Mon Dec 14 04:17:21 2015 -0800
    16.3 @@ -14,6 +14,7 @@
    16.4  	"time"
    16.5  
    16.6  	"code.secondbit.org/pass.hg"
    16.7 +	"code.secondbit.org/scopes.hg/types"
    16.8  	"code.secondbit.org/uuid.hg"
    16.9  	"github.com/gorilla/mux"
   16.10  )
   16.11 @@ -413,11 +414,11 @@
   16.12  	encode(w, r, http.StatusOK, Response{Sessions: []Session{session}, Errors: errors})
   16.13  }
   16.14  
   16.15 -func credentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
   16.16 +func credentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool) {
   16.17  	enc := json.NewEncoder(w)
   16.18  	username := r.PostFormValue("username")
   16.19  	password := r.PostFormValue("password")
   16.20 -	scopes = stringsToScopes(strings.Split(r.PostFormValue("scope"), " "))
   16.21 +	scopes = scopeTypes.StringsToScopes(strings.Split(r.PostFormValue("scope"), " "))
   16.22  	profile, err := authenticate(username, password, context)
   16.23  	if err != nil {
   16.24  		if isAuthError(err) {
    17.1 --- a/token.go	Sat Jul 18 03:38:27 2015 -0400
    17.2 +++ b/token.go	Mon Dec 14 04:17:21 2015 -0800
    17.3 @@ -8,6 +8,7 @@
    17.4  	"strings"
    17.5  	"time"
    17.6  
    17.7 +	"code.secondbit.org/scopes.hg/types"
    17.8  	"code.secondbit.org/uuid.hg"
    17.9  
   17.10  	"github.com/dgrijalva/jwt-go"
   17.11 @@ -46,7 +47,7 @@
   17.12  	CreatedFrom  string
   17.13  	ExpiresIn    int32
   17.14  	TokenType    string
   17.15 -	Scopes       Scopes
   17.16 +	Scopes       scopeTypes.Scopes
   17.17  	ProfileID    uuid.ID
   17.18  	ClientID     uuid.ID
   17.19  	Revoked      bool
   17.20 @@ -180,7 +181,7 @@
   17.21  	return tokens, nil
   17.22  }
   17.23  
   17.24 -func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
   17.25 +func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool) {
   17.26  	enc := json.NewEncoder(w)
   17.27  	refresh := r.PostFormValue("refresh_token")
   17.28  	if refresh == "" {
    18.1 --- a/token_test.go	Sat Jul 18 03:38:27 2015 -0400
    18.2 +++ b/token_test.go	Mon Dec 14 04:17:21 2015 -0800
    18.3 @@ -5,6 +5,7 @@
    18.4  	"testing"
    18.5  	"time"
    18.6  
    18.7 +	"code.secondbit.org/scopes.hg/types"
    18.8  	"code.secondbit.org/uuid.hg"
    18.9  )
   18.10  
   18.11 @@ -64,7 +65,7 @@
   18.12  		Created:      time.Now().Round(time.Millisecond),
   18.13  		ExpiresIn:    3600,
   18.14  		TokenType:    "bearer",
   18.15 -		Scopes:       stringsToScopes([]string{"scope"}),
   18.16 +		Scopes:       scopeTypes.StringsToScopes([]string{"scope"}),
   18.17  		ProfileID:    uuid.NewID(),
   18.18  	}
   18.19  	for _, store := range tokenStores {