ponder

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2023 License: MIT Imports: 10 Imported by: 0

README

Ponder

Introduction

Ponder is a go language helper for RethinkDB.

Ponder implements a file-system like abstraction, with Rethinkdb tables as directories and documents stored as files, with Unix-like per document permission system. It also contains various conveniences on top of the standard driver.

Using Ponder as a library

Start with Open(url) to create a System, then you can work directly on the System for convenient usage without permission. Or, for restricted access, use System.Shell() to log in to the ponder system.

To overwrite the standard login method, set System.Login to your own login method.

To store data that the Shell can work with, each document should use a globally unique ID over all entries in the database. ID such as a UUID, a ULID, or a URL are required, because ponder tracks the document info and permissions of all documents in a single table with this ID.

Go doc documentation is available, use it to understand ponder better.

Command line tool

In cmd/ponder a command line tool is available to administer the ponder permission system on the command line.

The tool is also a good example of how to use Ponder as a library. It is also a good way to test the Ponder library itself.

Use ponder help for more information.

Changes

v0.1.4: Admin users can bypass group permissions if they are part of that group.

Documentation

Overview

package ponder is a go language helper for RethinkDB.

Ponder implements a file-system like abstraction, with Rethinkdb tables as directories and documents stored as files, with Unix-like per document permission system. It also contains various conveniences on top of the standard driver.

Index

Constants

View Source
const DocInfoField = "_ponder_docinfo"

DocInfoField is the field used to store docinfo in

View Source
const ErrorCheckAccessDenied = Error("Check: access denied")
View Source
const ErrorDocInfoSetNil = Error("DocInfoSet: cannot set nil document info")
View Source
const ErrorPutAllIdsDoNotMatchObjects = Error("PutAll: IDs do not match objects")
View Source
const ErrorReadDirDoesNotExist = Error("ReadDir: dir does not exist")
View Source
const ErrorRmDirNotEmpty = Error("RmDir: dir is not empty")
View Source
const ErrorShellCannotLoadGroups = Error("Shell: cannot load user groups")

Variables

This section is empty.

Functions

func AnyToArray

func AnyToArray[Type any](array []any) []Type

ArrayToAny converts an array of any to an array of Type

func ArrayToAny

func ArrayToAny[Type any](array []Type) []any

ArrayToAny converts an array of type Type to an array of any

func CheckDocInfos

func CheckDocInfos(user User, needed Mode, infos []DocInfo) ([]ID, []ID)

CheckDocInfos return a list of DocInfo IDs for which the check is OK, and one for which it is not ok.

func CheckUserDocInfo

func CheckUserDocInfo(user User, needed Mode, qe r.QueryExecutor, multiple bool, docInfo r.Term, table r.Term) r.Term

Check fetches the docinfo using the dockinfo term and checks if the user has access with the needed mode. The docinfo selection should select the matching docinfo for the documents to load. Multiple should be set to trtue if the goal is to check multiple documents in stead of one. The function returns a term, based on table, that that will use Get or GetAll on table to return the documents the user is allowed to see. If the user cannot see any documents an error term with ErrorCheckAccessDenied is returned in stead. An error term is also returned if the fetchuing of the docinfo fails somehow.

func DirReadAllUnchecked

func DirReadAllUnchecked[Type any](dw *DirReader, array *[]Type) (*[]Type, error)

DirReadAllUnchecked generically stores an array of Ponder objecst or document to the dir. This is a low level function that does not do any permission checks. If the object exists it will be overwritten.

func DirReadUnchecked

func DirReadUnchecked[Type any](dr *DirReader, object Type) (Type, error)

DirReadUnchecked generically stores a Ponder object or document to the dir. This is a low level function that does not do any permission checks. If the object exists it will be overwritten.

func DirWriteAllUnchecked

func DirWriteAllUnchecked[Type any](dw *DirWriter, array []Type) ([]Type, error)

DirWriteAllUnchecked generically stores an array of Ponder objecst or document to the dir. This is a low level function that does not do any permission checks. If the object exists it will be overwritten.

func DirWriteUnchecked

func DirWriteUnchecked[Type any](dw *DirWriter, object Type) (Type, error)

DirWriteUnchecked generically stores a Ponder object or document to the dir. This is a low level function that does not do any permission checks. If the object exists it will be overwritten.

func StringsToStrings

func StringsToStrings[Type interface{ ~string }, Sub interface{ ~string }](array []Sub) []Type

StringsToStrings converts an array of string-likes a subtype of Type to an array of Type which must also be a sybtype of string.

func SystemDelete added in v0.1.3

func SystemDelete(system *System, path FileName, id ID) error

func SystemGet added in v0.1.3

func SystemGet[Type any](system *System, path FileName, id ID) (*Type, error)

func SystemPut added in v0.1.3

func SystemPut[Type any](system *System, path FileName, object *Type) (*Type, error)

func SystemUpdate added in v0.1.3

func SystemUpdate[Type any](system *System, path FileName, id ID, object *Type) (*Type, error)

Types

type Creds

type Creds interface {
	// Creds returns the credentials data as a Header
	Creds() Header
}

Creds is an interface that models credentials a user has to present to log in to the Ponder System. PasswordCreds are provided.

type Dir

type Dir struct {
	// system is the underlying system, inherited
	*System `json:"-" rethinkdb:"-"`

	// Virtual filename of this Dir
	FileName `json:"file_name" rethinkdb:"file_name"`

	// PK is the name of the primary key that will be used for this dir
	// If empty, Ponder uses "id".
	PK string `json:"pk,omitempty" rethinkdb:"pk,omitempty"`

	// Table is the table expression for this dir
	Table r.Term `json:"-" rethinkdb:"-"`

	// Shell that is using this dir. May be null if the dir is being used
	// by the System, and no shell is connected
	Shell *Shell `json:"-" rethinkdb:"-"`
}

Dir is a virtual directory with documents, mapped to a rethinkdb table.

func NewDir

func NewDir(sys *System, fn FileName, pk ...string) *Dir

NewDir return a new dir but does NOT save it.

func (*Dir) All

func (d *Dir) All() *DirReader

GetAll returns a DirReader for reading multiple documents. It will return all documents.

func (Dir) DocTable

func (d Dir) DocTable() r.Term

func (*Dir) Get

func (d *Dir) Get(key any) *DirReader

Get returns a DirReader for reading a single document by ID.

func (*Dir) GetMany

func (d *Dir) GetMany(keys ...any) *DirReader

GetMany returns a DirReader for reading multiple documents by ID.

func (*Dir) Put

func (d *Dir) Put() *DirWriter

Put returns a DirWriter that will overwrite existing records.

func (*Dir) PutIfNew

func (d *Dir) PutIfNew() *DirWriter

Put returns a DirWriter that will NOT overwrite existing records, and only write new records.

func (*Dir) Update

func (d *Dir) Update() *DirWriter

Update returns a DirWriter that will update/merge existing records.

type DirReader

type DirReader struct {
	// Dir this was derived from.
	*Dir `json:"-" rethinkdb:"-"`
	// Condition for reading.
	Condition r.Term `json:"-" rethinkdb:"-"`
	// DocInfo is the docinfo expression for this DirReader
	DocInfo r.Term `json:"-" rethinkdb:"-"`
	// Multiple is true if this reader is for reading multiple objects
	Multiple bool
}

DirReader allows to specify read conditions easily

func (*DirReader) AllUnchecked

func (dr *DirReader) AllUnchecked(array any) (any, error)

AllUnchecked gets an array of objects from the dir with given ID. This is a low level function that does not do any permission checks. Will return an error if no results were found.

func (*DirReader) Check

func (dr *DirReader) Check(needed Mode) *DirReader

Check fetches the docinfo and checks if the user of Dir.Shell has access with the needed mode. If Dir.Shell is nil, this does nothing. The reader modified to become a getAll which only includes the documents to which access is allowed, possibly ending up with an empty list.

func (*DirReader) Delete

func (dr *DirReader) Delete() error

Delete deletes all matches for the DirReader. This function calls Check first with ModeWrite.

func (*DirReader) DeleteUnchecked

func (dr *DirReader) DeleteUnchecked() error

DeleteUnchecked deletes all matches for the DirReader. This is a low level function that does not do any permission checks.

func (*DirReader) Read

func (dr *DirReader) Read(object any) (any, error)

Read gets a single object from the dir with given ID. This function calls Check first with ModeRead. Will return an error if no results were found.

func (*DirReader) ReadAll

func (dr *DirReader) ReadAll(array any) (any, error)

AllRead gets an array of objects from the dir with given ID. This function calls Check first with ModeRead. Will return an error if no results were found.

func (*DirReader) ReadJSON

func (dr *DirReader) ReadJSON() (string, error)

ReadJSON gets a single object from the dir with given ID. The object is converted to a JSON string. This function calls Check first with ModeRead. Will return an error if no results were found.

func (*DirReader) Unchecked

func (dr *DirReader) Unchecked(object any) (any, error)

Unchecked gets a single object from the dir with given ID. This is a low level function that does not do any permission checks. Will return an error if no results were found.

func (*DirReader) UncheckedJSON

func (dr *DirReader) UncheckedJSON() (string, error)

UncheckedJSON gets a single object from the dir with given ID. The object is converted to a JSON string. This is a low level function that does not do any permission checks. Will return an error if no results were found.

type DirWriter

type DirWriter struct {
	// Dir this was derived from.
	*Dir `json:"-" rethinkdb:"-"`
	// Condition for writing.
	Condition r.Term `json:"-" rethinkdb:"-"`
	// Options for inserting
	r.InsertOpts `json:"-" rethinkdb:"-"`
	// DocInfo is the docinfo expression for this DirWriter
	DocInfo r.Term `json:"-" rethinkdb:"-"`
}

DirWriter allows to specify options on writing easily.

func (DirWriter) All

func (dw DirWriter) All(objects map[ID]any) ([]any, error)

All writes an array of objects by ID. The IDs are needed to be able to check the permissions. This function calls Check first with ModeWrite. It also creates Docinfo for any new documents.

func (DirWriter) AllUnchecked

func (dw DirWriter) AllUnchecked(array []any) ([]any, error)

Unchecked writes an array. This is a low level function that does not do any permission checks.

func (*DirWriter) Check

func (dw *DirWriter) Check(needed Mode, objects map[ID]any) ([]DocInfo, error)

Check returns an error if the write is blocked by the permissions. If the ID does not exist in the docinfo, then it is considered that the document is new, and that docinfo must be generated for it, since writing a new doc is always allowed. If Dir.Shell is nil, this does nothing.

func (DirWriter) JSON

func (dw DirWriter) JSON(id ID, jsonStr string) (any, error)

JSOM writes a single JSON string. This function calls Check first with ModeWrite. It also creates Docinfo for any new documents.

func (DirWriter) One

func (dw DirWriter) One(id ID, object any) (any, error)

One writes a single object. This function calls Check first with ModeWrite. It also creates Docinfo for any new documents.

func (DirWriter) Unchecked

func (dw DirWriter) Unchecked(object any) (any, error)

Unchecked writes a single object. This is a low level function that does not do any permission checks.

func (DirWriter) UncheckedJSON

func (dw DirWriter) UncheckedJSON(jsonStr string) (any, error)

Unchecked writes a single JSON string. This is a low level function that does not do any permission checks.

type Doc

type Doc interface {
	// Must be able to get/set documents
	DocInfoGetterSetter
}

Doc is a document stored with Ponder which has to be checked for access with the ponder permission system.

type DocInfo

type DocInfo struct {
	// ID of the document that this DocInfo applies to.
	// This is also used as the ID of the DocInfo itself
	// The ID should be a globally unique ID such as an UUID, ULID or URL.
	ID ID `json:"id" rethinkdb:"id"`

	// Mode of this document for users who are not a oner in users or groups.
	// Users and groups may have their own modes.
	Mode Mode `json:"mode" rethinkdb:"mode"`

	// Users are the IDs of the users who own this document mapped
	// to their Mode permissions.
	Users ModeMap `json:"users" rethinkdb:"users"`

	// Groups are the IDs of the groups who own this document mapped
	// to their Mode permissions.
	Groups ModeMap `json:"groups" rethinkdb:"groups"`

	// Mod is the modification time of the document.
	Mod time.Time `json:"mod" rethinkdb:"mod"`

	// Hide is the time the document was hidden, or nil if not hidden.
	Hide *time.Time `json:"hide" rethinkdb:"hide,omitempty"`

	// Header may contain extra data needed for this document.
	Header `json:"header,omitempty" rethinkdb:"header,omitempty"`
}

DocInfo keeps track of metadata of a ponder document. This includes the permissionsm users and groups of the document. It also keeps track of time stamps and hidden status of documents.

func TermGetDocInfo

func TermGetDocInfo(qe r.QueryExecutor, docInfo r.Term) ([]DocInfo, error)

TermGetDocInfo gets docinfo from a Term using an executor.

func (DocInfo) Allowed

func (di DocInfo) Allowed(user User, needed Mode) bool

Returns wheter or not a user is allowed to perform the action indicated by mode with this document.

func (*DocInfo) DocInfoGet

func (di *DocInfo) DocInfoGet() *DocInfo

DocInfoGet implements DocInfoGetter for DocInfo

func (*DocInfo) DocInfoSet

func (di *DocInfo) DocInfoSet(set *DocInfo) error

DocInfoSet implements DocInfoSetter for DocInfo

type DocInfoGetter

type DocInfoGetter interface {
	// DocInfoGet returns the docinfo for this object.
	// May return nil to indicate it is not available.
	DocInfoGet() *DocInfo
}

DocInfoGetter is an interface that returns the docinfo for a document.

type DocInfoGetterSetter

type DocInfoGetterSetter interface {
	DocInfoGetter
	DocInfoSetter
}

DocInfoGetterSetter is an interface that gets and sets the docinfo for a document.

type DocInfoSetter

type DocInfoSetter interface {
	// DocInfoSet returns the docinfo for this object.
	// Should return nil on success.
	// This function may optionally return an error if DocInfo is nil.
	DocInfoSet(*DocInfo) error
}

DocInfoSetter is an interface that sets the docinfo for a document.

type Docs

type Docs []Doc

Docs is an array of documents.

type Error

type Error string

func (Error) Error

func (e Error) Error() string

type FileName

type FileName string

FileName is used to idernify Dir and dOc by Ponder. It is a string but it shiould be Punicode-encodable.

func (FileName) Join

func (fn FileName) Join(sub FileName) FileName

Joins file names together.

func (FileName) Rethink

func (fn FileName) Rethink() string

Rethink is the name to use for Rethinkdb

func (FileName) String

func (fn FileName) String() string

String for FileName

type Group

type Group struct {
	// ID is the group id. It can be anything but is must be globally unique
	ID ID `json:"id" rethinkdb:"id"`

	// Link is the link to your own group document.
	Link ID `json:"link" rethinkdb:"link"`

	// Header may contain extra data needed for this user.
	Header `json:"header,omitempty" rethinkdb:"header,omitempty"`

	// Custom is a field that can be used to store your own custom data
	Custom any `json:"custom,omitempty" rethinkdb:"custom,omitempty"`
}

Group is a group who can have or not have access to a document in ponder. You can link this to the group in your own database using the UID. Ponder generates the "root" and the "nobody" groups on setup.

type Header = http.Header

Header is used as a catch-all for storing supplemental data in Ponder. Reuse the http Header for this.

type ID

type ID string

ID is a universally unique identifier used to identify and link to various objects in Ponder. It can be anything, but it should be globally unique in the Ponder system. Therefore, a URL, UUID, or ULID, or similar could be used for this. However for User IDs or Group IDs is is often simpler to use a name.

func NewDocID

func NewDocID(database, table, id string) ID

NewDocID makes and URL that points to a document, using the ponder scheme. A document with id quux in database foo and table bar will be referred to as ponder://foo/bar#quux

type Login

type Login interface {
	// CheckLogin should atttempt to log in the user with the given creds.
	// It should return nil if the user logged in sucessfully or an error if not.
	CheckLogin(user User, creds Creds) error
	// Write should write the login for this user, possibly using the creds.
	WriteLogin(user *User, creds Creds) error
	// DeleteLogin should delete the login info for this user, but not the user
	// iself. Requires creds to confirm.
	DeleteLogin(user *User, cred Creds) error
}

Login is an interface that models a method to login a user to the System. It also allows to update the credentials of the user. As a default a PasswordLogin that uses User.Hash is provided. Custom Login methods can use the User.Headers fields to store data.

type Mode

type Mode uint32
const (
	// ModeSetuid can modify the users of this document.
	ModeSetuid Mode = 0o4000
	// ModeSetgid can modify the groups of this document.
	ModeSetgid Mode = 0o2000
	// ModeSticky documents cannot be deleted by non-owners if set in Doc.Mode.
	ModeSticky Mode = 0o1000
	// ModeRead read the document
	ModeRead Mode = 0o004
	// ModeWrite can write the document
	ModeWrite Mode = 0o002
	// ModeList can list the document
	ModeList Mode = 0o001
)

type ModeMap

type ModeMap map[ID]Mode

ModeMap is map from ID to mode

type PasswordCreds

type PasswordCreds string

PasswordCreds are password credentials.

func (PasswordCreds) Creds

func (pwc PasswordCreds) Creds() Header

type PasswordLogin

type PasswordLogin struct {
	*System
}

PasswordLogin is a Login method that simply uses a bcrypt hash password

func (PasswordLogin) CheckLogin

func (pwl PasswordLogin) CheckLogin(user User, creds Creds) error

func (PasswordLogin) DeleteLogin

func (pwl PasswordLogin) DeleteLogin(user *User, creds Creds) error

func (PasswordLogin) WriteLogin

func (pwl PasswordLogin) WriteLogin(user *User, creds Creds) error

type Shell

type Shell struct {
	// System this shell is for
	*System `json:"system" rethinkdb:"system"`

	// User who is using the shell
	*User `json:"user" rethinkdb:"user"`

	// Groups of the user, cached
	Goups []Group `json:"groups" rethinkdb:"groups"`
}

Shell is a virtual shell session on a System that limits the permissions of the logged in User to what matches their permissions and rights.

func (*Shell) Dir

func (s *Shell) Dir(fn FileName, key ...string) *Dir

Returns a protected dir. Use this in addition to Dir.Check().

type System

type System struct {
	// Name of the underlying database
	Name string `json:"name" rethinkdb:"name"`
	// QueryExecutor to wrap
	r.QueryExecutor `json:"-" rethinkdb:"-"`
	// Mock, if any
	Mock *r.Mock `json:"-" rethinkdb:"-"`
	// Session if any
	Session *r.Session `json:"-" rethinkdb:"-"`
	// Prefix to use for Ponder functionality, _ponder by default
	Prefix FileName `json:"prefix" rethinkdb:"prefix"`
	// SystemDir inherited.
	SystemDir `json:"system_dir" rethinkdb:"system_dir"`
	// The login to use. Set to PassordLogin by default.
	// Overwrite this to change the login method
	Login `json:"-" rethinkdb:"-"`
}

System is a virtual file system and wrapper around a connection to a rethinkdb that has the convenience it might be mocked if needed. The methods of System do ignore the Ponder permission system, and all RethinkDB queries are executed zith the Rethinkdb privileges of the connected RethinkDB user. To uset he permission system use Login to obtain a Shell.

func Open

func Open(urlString string) (*System, error)

Open opens a connection for a System and sets up Ponder on it. The urlstring should begin with rethinkdb:// for a real connection or rethinkdb+mock:// for a mock. The following options can be set in the query:

  • ponder_prefix: prefix to use for ponder related tables, zzz_ponder default

func OpenWithoutPrepare

func OpenWithoutPrepare(urlString string) (*System, error)

OpenWithoutPrepare opens a connection for a System but does not set up Ponder on it. It does call Wait to wait util the database is ready.

func (*System) Close

func (s *System) Close() error

Close closes the system.

func (*System) DirExists

func (s *System) DirExists(fn FileName) bool

DirExists returns whether or not the dir exists in the drive.

func (*System) GetDocInfo

func (s *System) GetDocInfo(id ID) (*DocInfo, error)

GetDocInfo gets the docinfo for the given resource id. This can be used for permission checking or checking the modtime, etc.

func (*System) GetDocInfos

func (s *System) GetDocInfos(ids []ID) (*[]DocInfo, error)

GetDocInfos gets the docinfo for the given resource ids. This can be used for permission checking or checking the modtime, etc.

func (*System) Mkdir

func (s *System) Mkdir(fn FileName, keys ...string) (*Dir, error)

MkDir makes a dir with the given key, if any. Waits for the table to be fully created.

func (*System) MkdirIfNew

func (s *System) MkdirIfNew(fn FileName, pk ...string) (*Dir, error)

MkDir makes a dir with the given primary key, if any, if it doesn't exist. Returns the dir for further use unless if there was an error.

func (*System) Prepare

func (s *System) Prepare() error

Prepare prepares the System for use with Ponder.

func (*System) PutDocInfo

func (s *System) PutDocInfo(info DocInfo) error

PutDocInfo writes docinfo to the System's Doc Dir. The docinfo is used to determine the pernnissions of the document with the Id of info. Therefor it is neccerqy to use qgrlobqlly unique ID, such as a UUID, IRL or ULID.

func (*System) ReadDir

func (s *System) ReadDir(fn FileName) (*Dir, error)

ReadDir reads in a dir if exists

func (*System) RmDir

func (s *System) RmDir(fn FileName) error

RmDir deletes a dir if exists, but only if it is empty.

func (*System) Shell

func (s *System) Shell(userID ID, creds Creds) (*Shell, error)

Shell starts a shell session in the System for the given Ponder user. Inside the shell the access is limited to what the user's permissions allow.

func (*System) Wait added in v0.1.3

func (s *System) Wait() error

Wait waits until the rethinkdb is completely ready.

func (*System) WaitForTable added in v0.1.3

func (s *System) WaitForTable(tableName string) error

WaitForTable waits until the named rethinkdb table is completely ready.

type SystemDir

type SystemDir struct {
	// Doc dir
	Doc *Dir `json:"user" rethinkdb:"doc"`
	// User dir
	User *Dir `json:"doc" rethinkdb:"user"`
	// Group dir
	Group *Dir `json:"group" rethinkdb:"group"`
	// Version dir
	Version *Dir `json:"version" rethinkdb:"version"`
}

SystemDir contains all Ponder specific Dirs

type User

type User struct {
	// ID is the ID of the user. It can be anything but it must be globally
	// unique. It is also used as the login name of this user.
	ID ID `json:"id" rethinkdb:"id"`

	// Groups are the IDs of the groups to which this user belongs.
	Groups []ID `json:"groups" rethinkdb:"groups"`

	// Link is the link to your own user document.
	Link ID `json:"link" rethinkdb:"link"`

	// Header may contain extra data needed for this user.
	Header `json:"header,omitempty" rethinkdb:"header,omitempty"`

	// Root is true if this is a root user.
	// Root users can bypass all permissions for all Ponder documents.
	Root bool `json:"root" rethinkdb:"root"`

	// Admin is true if this is an admin user.
	// Admin users can bypass permissions for Ponder documents that
	// belong to one of the groups the admin is in, but not for
	// documents that they are not in the group for.
	Admin bool `json:"admin" rethinkdb:"admin"`

	// Login is the Login linked to the ponder User
	Login `json:"-" rethinkdb:"-"`

	// Custom is a field that can be used to store your own custom data
	Custom any `json:"custom,omitempty" rethinkdb:"custom,omitempty"`
}

User is a user who can have or not have access to a document in Ponder. You can link this to the user in your own database using the UID and the Authericator interface. This is different from the RethinkDB user. Ponder generates the "root" and the "nobody" users on setup.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL