Skip to content
Permalink
Browse files

Added oauth handlers and tests with mocks. Part of T705.

  • Loading branch information
ngerakines committed Dec 19, 2019
1 parent e16ea3b commit 7a0863f71b2504583e0537aa18664a5073d7f642
Showing with 585 additions and 9 deletions.
  1. +1 −1 admin.go
  2. +9 −1 app.go
  3. +9 −0 config/config.go
  4. +80 −0 database.go
  5. +4 −5 go.mod
  6. +2 −0 go.sum
  7. +2 −2 handle.go
  8. +18 −0 main_test.go
  9. +252 −0 oauth.go
  10. +10 −0 oauth/state.go
  11. +198 −0 oauth_test.go
@@ -260,7 +260,7 @@ func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *ht
}
if err != nil {
log.Error("toggle user suspended: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not toggle user status: %v")}
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not toggle user status: %v", err)}
}
return impart.HTTPError{http.StatusFound, fmt.Sprintf("/admin/user/%s#status", username)}
}
10 app.go
@@ -70,7 +70,7 @@ type App struct {
cfg *config.Config
cfgFile string
keys *key.Keychain
sessionStore *sessions.CookieStore
sessionStore sessions.Store
formDecoder *schema.Decoder

timeline *localTimeline
@@ -101,6 +101,14 @@ func (app *App) SetKeys(k *key.Keychain) {
app.keys = k
}

func (app *App) SessionStore() sessions.Store {
return app.sessionStore
}

func (app *App) SetSessionStore(s sessions.Store) {
app.sessionStore = s
}

// Apper is the interface for getting data into and out of a WriteFreely
// instance (or "App").
//
@@ -92,6 +92,15 @@ type (
LocalTimeline bool `ini:"local_timeline"`
UserInvites string `ini:"user_invites"`

// OAuth
EnableOAuth bool `ini:"enable_oauth"`
OAuthProviderAuthLocation string `ini:"oauth_auth_location"`
OAuthProviderTokenLocation string `ini:"oauth_token_location"`
OAuthProviderInspectLocation string `ini:"oauth_inspect_location"`
OAuthClientCallbackLocation string `ini:"oauth_callback_location"`
OAuthClientID string `ini:"oauth_client_id"`
OAuthClientSecret string `ini:"oauth_client_secret"`

// Defaults
DefaultVisibility string `ini:"default_visibility"`
}
@@ -11,8 +11,12 @@
package writefreely

import (
"context"
"crypto/rand"
"database/sql"
"fmt"
"github.com/pkg/errors"
"math/big"
"net/http"
"strings"
"time"
@@ -2453,6 +2457,59 @@ func (db *datastore) GetCollectionLastPostTime(id int64) (*time.Time, error) {
return &t, nil
}

func (db *datastore) GenerateOAuthState(ctx context.Context) (string, error) {
state, err := randString(24)
if err != nil {
return "", err
}
_, err = db.ExecContext(ctx, "INSERT INTO oauth_client_state (state, used, created_at) VALUES (?, FALSE, NOW())", state)
if err != nil {
return "", fmt.Errorf("unable to record oauth client state: %w", err)
}
return state, nil
}

func (db *datastore) ValidateOAuthState(ctx context.Context, state string) error {
res, err := db.ExecContext(ctx, "UPDATE oauth_client_state SET used = TRUE WHERE state = ?", state)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected != 1 {
return fmt.Errorf("state not found")
}
return nil
}

func (db *datastore) RecordRemoteUserID(ctx context.Context, localUserID, remoteUserID int64) error {
var err error
if db.driverName == driverSQLite {
_, err = db.ExecContext(ctx, "INSERT OR REPLACE INTO users_oauth (user_id, remote_user_id) VALUES (?, ?)", localUserID, remoteUserID)
} else {
_, err = db.ExecContext(ctx, "INSERT INTO users_oauth (user_id, remote_user_id) VALUES (?, ?) "+db.upsert("user_id"), localUserID, remoteUserID)
}
if err != nil {
log.Error("Unable to INSERT users_oauth for '%d': %v", localUserID, err)
}
return err
}

// GetIDForRemoteUser returns a user ID associated with a remote user ID.
func (db *datastore) GetIDForRemoteUser(ctx context.Context, remoteUserID int64) (int64, error) {
var userID int64 = -1
err := db.
QueryRowContext(ctx, "SELECT user_id FROM users_oauth WHERE remote_user_id = ?", remoteUserID).
Scan(&userID)
// Not finding a record is OK.
if err != nil && err != sql.ErrNoRows {
return -1, err
}
return userID, nil
}

// DatabaseInitialized returns whether or not the current datastore has been
// initialized with the correct schema.
// Currently, it checks to see if the `users` table exists.
@@ -2483,3 +2540,26 @@ func handleFailedPostInsert(err error) error {
log.Error("Couldn't insert into posts: %v", err)
return err
}

func randString(length int) (string, error) {
// every printable character on a US keyboard
charset := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
out := make([]rune, length)

setLen := big.NewInt(int64(len(charset)))
for idx := 0; idx < length; idx++ {
offset, err := rand.Int(rand.Reader, setLen)
if err != nil {
return "", err
}

if !offset.IsUint64() {
// this should (in theory) never happen
return "", errors.Errorf("Non-Uint64 offset returned from rand.Int")
}

out[idx] = charset[offset.Uint64()]
}

return string(out), nil
}
9 go.mod
@@ -29,20 +29,18 @@ require (
github.com/nicksnyder/go-i18n v1.10.0 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/pkg/errors v0.8.1
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/stretchr/testify v1.3.0
github.com/writeas/activity v0.1.2
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2
github.com/writeas/httpsig v1.0.0
github.com/writeas/impart v1.1.0
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219
github.com/writeas/nerds v1.0.0
github.com/writeas/openssl-go v1.0.0 // indirect
github.com/writeas/saturday v1.7.1
github.com/writeas/slug v1.2.0
github.com/writeas/web-core v1.2.0
@@ -55,6 +53,7 @@ require (
google.golang.org/appengine v1.4.0 // indirect
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect
gopkg.in/ini.v1 v1.41.0
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

go 1.13
2 go.sum
@@ -129,6 +129,7 @@ github.com/writeas/nerds v1.0.0 h1:ZzRcCN+Sr3MWID7o/x1cr1ZbLvdpej9Y1/Ho+JKlqxo=
github.com/writeas/nerds v1.0.0/go.mod h1:Gn2bHy1EwRcpXeB7ZhVmuUwiweK0e+JllNf66gvNLdU=
github.com/writeas/openssl-go v1.0.0 h1:YXM1tDXeYOlTyJjoMlYLQH1xOloUimSR1WMF8kjFc5o=
github.com/writeas/openssl-go v1.0.0/go.mod h1:WsKeK5jYl0B5y8ggOmtVjbmb+3rEGqSD25TppjJnETA=
github.com/writeas/saturday v1.6.0/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/saturday v1.7.1 h1:lYo1EH6CYyrFObQoA9RNWHVlpZA5iYL5Opxo7PYAnZE=
github.com/writeas/saturday v1.7.1/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g=
@@ -141,6 +142,7 @@ github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAv
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f h1:ETU2VEl7TnT5bl7IvuKEzTDpplg5wzGYsOCAPhdoEIg=
golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -73,7 +73,7 @@ type (

type Handler struct {
errors *ErrorPages
sessionStore *sessions.CookieStore
sessionStore sessions.Store
app Apper
}

@@ -96,7 +96,7 @@ func NewHandler(apper Apper) *Handler {
InternalServerError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}")),
Blank: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}")),
},
sessionStore: apper.App().sessionStore,
sessionStore: apper.App().SessionStore(),
app: apper,
}

@@ -0,0 +1,18 @@
package writefreely

import (
"encoding/gob"
"math/rand"
"os"
"testing"
"time"
)

// TestMain provides testing infrastructure within this package.
func TestMain(m *testing.M) {
rand.Seed(time.Now().UTC().UnixNano())

gob.Register(&User{})

os.Exit(m.Run())
}

0 comments on commit 7a0863f

Please sign in to comment.
You can’t perform that action at this time.