Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
15002f0
Setup auth plugin.
lukebp Mar 11, 2022
497ebf4
Merge branch 'master' into auth
lukebp Jul 5, 2022
bd45077
Merge branch 'master' into auth
lukebp Jul 5, 2022
588d3c5
Refactor http/v3 errors.
lukebp Jul 6, 2022
88a4de8
Continue to refine the plugin model.
lukebp Jul 6, 2022
3ce2109
Continue to refine the plugin model.
lukebp Jul 7, 2022
9b5a854
Add app framework.
lukebp Jul 7, 2022
d89d375
Layout auth plugin.
lukebp Jul 7, 2022
50d631c
Refine the user authorization model.
lukebp Jul 7, 2022
ecbfae4
Continue to refine plugin model.
lukebp Jul 8, 2022
c9d0caf
Add the app model.
lukebp Jul 8, 2022
16ff5ef
plugins/auth: Add local session type.
lukebp Jul 12, 2022
277bb7c
Merge branch 'master' into auth
lukebp Jul 13, 2022
0a12d30
auth: Implement SetCmdPerms.
lukebp Jul 14, 2022
9815211
Cleanup.
lukebp Jul 15, 2022
2ae1190
Move Cmds() from plugin to app.
lukebp Jul 15, 2022
eac9b7d
plugins/auth: Partially implement Authorize().
lukebp Jul 15, 2022
7af28c9
Simplify user model.
lukebp Jul 16, 2022
ac7c481
plugins/auth: Finish Authorize().
lukebp Jul 16, 2022
47d22a7
Continue to refine the app model.
lukebp Jul 16, 2022
e074bc7
Implement readCmd().
lukebp Jul 16, 2022
6f8d007
Merge branch 'master' into auth
lukebp Jul 18, 2022
db13055
Cleanup.
lukebp Jul 18, 2022
1defea3
auth: Begin CmdNewUser.
lukebp Jul 18, 2022
dcb739e
auth: Work on CmdNewUser.
lukebp Jul 20, 2022
158eec2
auth: Add db outline.
lukebp Jul 20, 2022
8f63a44
auth: Work on CmdNewUser.
lukebp Jul 20, 2022
727ea66
Remove global app user.
lukebp Jul 20, 2022
0ff3077
Remove app user db package.
lukebp Jul 20, 2022
28700f9
app: Make authorize() batched.
lukebp Jul 20, 2022
06ab346
Del erroneous file.
lukebp Jul 21, 2022
db8bd1c
Add user groups.
lukebp Jul 21, 2022
5613ce1
auth: Add email verification to CmdNewUser.
lukebp Jul 21, 2022
2b11c56
auth: Finish CmdNewUser.
lukebp Jul 22, 2022
f300d6c
Add proctl CLI tool.
lukebp Jul 23, 2022
197b590
proctl: Add http client.
lukebp Jul 23, 2022
4c3af8f
proctl: Get CSRF and cookies hooked up.
lukebp Jul 24, 2022
d54376c
proctl: Improve req/resp logging.
lukebp Jul 25, 2022
2dfe4de
Cleanup.
lukebp Jul 25, 2022
6d1688b
Config updates.
lukebp Jul 25, 2022
6d8f89a
Continue getting things hooked up.
lukebp Jul 26, 2022
804bdaf
auth: Add db methods.
lukebp Jul 26, 2022
c72d24d
auth: Add login, logout, and me cmds.
lukebp Jul 27, 2022
3010ffe
Package and deadcode cleanup.
lukebp Jul 28, 2022
7773c9b
legacypoliteia: Handle trillian edge case failure.
lukebp Jul 28, 2022
4645478
tstore: Improve blobs not found error.
lukebp Jul 28, 2022
c54666c
sessions: Cleanup expired sessions on startup.
lukebp Jul 27, 2022
098ee14
sessions: Add tests.
lukebp Jul 28, 2022
13402d2
auth: Remove session expiration.
lukebp Jul 29, 2022
ec19b13
Logging improvements.
lukebp Jul 29, 2022
c340c6f
Setup read route.
lukebp Jul 29, 2022
176c308
Remove plugin hook method.
lukebp Jul 29, 2022
bb74026
Cleanup.
lukebp Jul 29, 2022
c431e33
auth: Start adding custom user groups.
lukebp Jul 29, 2022
dcd66c5
auth: Continue implementing UpdateGroup.
lukebp Jul 29, 2022
f8d0235
Cleanup.
lukebp Jul 31, 2022
49b2472
More cleanup.
lukebp Jul 31, 2022
42aca8a
Merge branch 'master' into auth
lukebp Aug 9, 2022
35b8cb5
Add split server architecture.
lukebp Jul 31, 2022
f6a9e0c
Linter errors.
lukebp Aug 10, 2022
4062503
Add update user path.
lukebp Aug 10, 2022
173b63e
Finish UpdateGroups.
lukebp Aug 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package app

import (
"context"
)

// App provides an API for accessing the plugin configuration of a politeia
// app. An app is essentially just a unique configuration of plugins.
type App interface {
// TODO this needs to return reads and writes differently so that the
// server can validate them appropriately.
// Cmds returns the plugin commands that are part of the app.
Cmds() []CmdDetails

// PreventBatchedReads returns a list of plugin commands that are not
// allowed to be included in a read batch.
//
// Prior to executing a read batch, the politeia server will verify that the
// read commands are allowed to be executed as part of a read batch. This
// allows the app prevent expensive reads from being batched. By default, all
// read commands are allowed to be batched.
PreventBatchedReads() []CmdDetails

// TODO I don't think I need to pass the context anymore since it's
// all one binary now

// Write executes a plugin write command.
//
// Any updates made to the session will be persisted by the politeia server.
Write(context.Context, *Session, Cmd) (*CmdReply, error)

// Read executes a read-only plugin command.
Read(context.Context, Session, Cmd) (*CmdReply, error)

// ReadBatch executes a batch of read-only plugin command.
// ReadBatch(context.Context, Session, []Cmd) ([]CmdReply, error)
}
78 changes: 78 additions & 0 deletions app/authmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package app

import (
"fmt"
"strings"
)

// TODO split these up into a session manager and an authorizer

// AuthManager provides user authorization for plugin commands.
type AuthManager interface {
// SessionUserID returns the user ID from the session values if one exists.
// An empty string is returned if a user ID does not exist.
SessionUserID(Session) string

// Authorize checks if the user is authorized to execute a list of plugin
// commands. This includes verifying that the user session is valid and that
// the user has the correct permissions to execute the commands.
//
// Configuring the session max age and checking for expired sessions is
// handled in the server layer. This method does not need to worry about
// checking for exipred sessions. Expired sessions will never make it to the
// app layer.
//
// A UserErr is returned if the user is not authorized to execute one or more
// of the provided commands.
//
// Changes made to the Session are not persisted by the politeia server.
Authorize(AuthorizeArgs) error
}

// AuthorizeArgs contains the arguments for the Authorize method.
type AuthorizeArgs struct {
Session Session
Cmds []CmdDetails
}

// String returns a string representation of the authorize structure.
func (a *AuthorizeArgs) String() string {
var cmds strings.Builder
for _, v := range a.Cmds {
cmds.WriteString(v.String())
}
return fmt.Sprintf("%v %+v", cmds.String(), a.Session.Values())
}

// TODO this belongs in the auth plugin
// UserGroup represents a custom user group.
//
// Apps set command permissions by assigning the command a list of user groups
// that are allowed to execute the command. The AuthManager plugin will have
// default user groups that can be used, but an app may also want to create
// user groups that are specific to the app's functionality.
//
// For example, a forum app may want to add a custom forum moderator group. The
// forum moderator group can be given permission to run commands related to
// moderating forum content, but without giving them access to other admin
// commands.
type UserGroup struct {
Group string

// AssignedBy contains all of the user groups that are allowed to assign
// this custom user group.
AssignedBy []string
}

// TODO this belongs in the auth plugin
// CmdPerms represents the permissions for a plugin command.
type CmdPerms struct {
Cmd CmdDetails

// Groups contains the user groups that are allowed to execute the command.
Groups []string
}
165 changes: 165 additions & 0 deletions app/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package app

import (
"context"
"database/sql"
"time"

"github.com/pkg/errors"
)

// Driver provides a standardized set of methods for executing plugin commands
// so that apps do not have to re-implement this execution logic.
//
// This logic resides in the app layer and not in the politeia server because,
// as of writing this, I'm not aware of a way to pass a sql transaction from
// the backend to the app if we are using golang plugins (or some variant)
// where the app is run as a separate process that communicates with the main
// politeia process via an RPC or gRPC connection. For this reason, all
// database transaction operations must be performed entirely in the app layer,
// creating the need for this driver.
type Driver struct {
plugins map[string]Plugin
db *sql.DB
authManager AuthManager
}

// NewDriver returns a new app Driver.
func NewDriver(plugins []Plugin, db *sql.DB, authMgr AuthManager) *Driver {
p := make(map[string]Plugin, len(plugins))
for _, v := range plugins {
p[v.ID()] = v
}
return &Driver{
plugins: p,
db: db,
authManager: authMgr,
}
}

// WriteCmd executes a plugin command that writes data.
func (d *Driver) WriteCmd(ctx context.Context, s *Session, cmd Cmd) (*CmdReply, error) {
// Setup the database transaction
tx, cancel, err := d.beginTx()
if err != nil {
return nil, err
}
defer cancel()

// Verify that the user is authorized
// to execute this plugin command.
err = d.authManager.Authorize(
AuthorizeArgs{
Session: *s,
Cmds: []CmdDetails{
{
Plugin: cmd.Plugin,
Version: cmd.Version,
Name: cmd.Name,
},
},
})
if err != nil {
return nil, err
}
userID := d.authManager.SessionUserID(*s)

// Execute the plugin command
p := d.plugin(cmd.Plugin)
reply, err := p.TxWrite(tx,
WriteArgs{
Cmd: cmd,
Session: s,
UserID: userID,
})
if err != nil {
return nil, err
}

// Commit the database transaction
err = tx.Commit()
if err != nil {
if err2 := tx.Rollback(); err2 != nil {
return nil, errors.Errorf("commit err: %v, rollback err: %v", err, err2)
}
return nil, errors.WithStack(err)
}

return reply, nil
}

// ReadCmd executes a read-only plugin command.
func (d *Driver) ReadCmd(ctx context.Context, s Session, cmd Cmd) (*CmdReply, error) {
// Verify that the user is authorized
// to execute this plugin command.
err := d.authManager.Authorize(
AuthorizeArgs{
Session: s,
Cmds: []CmdDetails{
{
Plugin: cmd.Plugin,
Version: cmd.Version,
Name: cmd.Name,
},
},
})
if err != nil {
return nil, err
}
userID := d.authManager.SessionUserID(s)

// Execute the plugin command
p := d.plugin(cmd.Plugin)
reply, err := p.Read(
ReadArgs{
Cmd: cmd,
UserID: userID,
})
if err != nil {
return nil, err
}

return reply, nil
}

// plugin returns a registered plugin.
func (d *Driver) plugin(pluginID string) Plugin {
return d.plugins[pluginID]
}

// beginTx returns a database transactions and a cancel function for the
// transaction.
//
// The cancel function can be used up until the tx is committed or manually
// rolled back. Invoking the cancel function rolls the tx back and releases all
// resources associated with it. This allows the caller to defer the cancel
// function in order to rollback the tx on unexpected errors. Once the tx is
// successfully committed the deferred invocation of the cancel function does
// nothing.
func (d *Driver) beginTx() (*sql.Tx, func(), error) {
ctx, cancel := ctxForTx()

opts := &sql.TxOptions{
Isolation: sql.LevelDefault,
}
tx, err := d.db.BeginTx(ctx, opts)
if err != nil {
return nil, nil, errors.WithStack(err)
}

return tx, cancel, nil
}

const (
// timeoutTx is the timeout for a database transaction.
timeoutTx = 3 * time.Minute
)

// ctxForTx returns a context and a cancel function for a database transaction.
func ctxForTx() (context.Context, func()) {
return context.WithTimeout(context.Background(), timeoutTx)
}
22 changes: 22 additions & 0 deletions app/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package app

import "fmt"

// UserErr represents an error that occurred during the execution of a plugin
// command and that was caused by the user.
type UserErr struct {
Code uint32
Context string
}

// Error satisfies the error interface.
func (e UserErr) Error() string {
if e.Context == "" {
return fmt.Sprintf("plugin user err: %v", e.Code)
}
return fmt.Sprintf("plugin user err: %v - %v", e.Code, e.Context)
}
Loading