Skip to main content

Brain contains a service to manage users and permissions

Project description

brain2_oc

pypi version Custom License

body_oc 2.0 service that handles users and permissions.

Please see LICENSE for further information.

See Releases for changes from release to release.

RESTlike Documentation

If you're only connecting to brain through the RESTlike API, check out the full documentation on the individual requests.

JavaScript/TypeScript

Also check out @ouroboros/brain on npm if you want to easily connect to brain in your choice of javascript / typescript framework.

Contents

Module Install

Requires

brain2_oc requires python 3.10 or higher

Install via pip

foo@bar:~$ pip install brain2_oc

[ top, contents ]

Module Configuration

Brain and all body_oc services use JSON to store their server side settings. For more information on how to setup and store these configuration files, visit config_oc.

Example Configuration

Below is a sample configuration file, we'll break it down section by section immediately after.

{
  "body": {
    "rest": {
      "allowed": [ "mydomain.com" ],
      "default": {
        "domain": "localhost",
        "host": "0.0.0.0",
        "protocol": "http"
      },
      "services": {
        "brain": { "port": 8000, "workers": 10 },
        "mouth": { "port": 8001, "workers": 2 }
      }
    }
  },

  "brain": {
    "data": "../.data",
    "google": false,
    "internal": {
      "redis": "session",
      "salt": "K_B.c6iUMTeESC@Z",
      "ttl": 5
    },
    "mysql": "records",
    "portals": {
      "my_app": {
        "rights": {
          "my_service_permission": 15,
          "my_other_permission": 2
        },
        "ttl": 7884000
      }
    },
    "recaptcha": false,
    "redis": "records",
    "send_error_emails": true,
    "user_default_locale": "en-US",
    "verbose": false
  },

  "email": {
    "error_to": "me+errors@mydomain.com",
    "from": "MyApp! <contact@mydomain.com>",
    "smtp": {
      "host": "smtp.mydomain.com",
      "port": 587,
      "tls": true,
      "user": "contact@mydomain.com",
      "passwd": "********"
    }
  },

  "memory": {
    "redis": "session"
  },

  "mysql": {
    "hosts": {
      "records": {
        "host": "sql.mydomain.com",
        "charset": "utf8mb4",
        "passwd": "********"
      }
    },
    "db": "my_app"
  },

  "redis": {
    "records": {
      "host": "redis.mydomain.com",
      "db": 0
    },
    "session": {
      "host": "redis.mydomain.com",
      "db": 1
    }
  }
}

[ top, contents, module configuration ]

Configuration Sections

First, we'll start with the lowest level things we control, the email/smtp, database, and caching software connection settings. Later we'll talk about other Ouroboros Coding software, the memory, body, and brain settings.

email section

  "email": {
    "error_to": "me+errors@mydomain.com",
    "from": "MyApp! <contact@mydomain.com>",
    "smtp": {
      "host": "smtp.mydomain.com",
      "port": 587,
      "tls": true,
      "user": "contact@mydomain.com",
      "passwd": "********"
    }
  }

This section will most likely exist in your project, but really only matters to brain if the send_error_emails config is set to true.

error_to represents the person or person's who will receive emails whenever brain has some sort of exception that wasn't handled.

from is the default email address the error email will come from.

smtp represents the SMTP server connection values.

[ top, contents, module configuration, configuration sections ]

mysql section

  "mysql": {
    "hosts": {
      "records": {
        "host": "sql.mydomain.com",
        "charset": "utf8mb4",
        "passwd": "********"
      }
    },
    "db": "my_app"
  }

Each object under hosts represents a named MySQL or MariaDB connection. The important part is the name, we only have one, records.

For more information on what can be entered under each name, see rest_mysql.

[ top, contents, module configuration, configuration sections ]

redis section

  "redis": {
    "records": {
      "host": "redis.mydomain.com",
      "db": 0
    },
    "session": {
      "host": "redis.mydomain.com",
      "db": 1
    }
  }

Each object under the "redis" section represents a named Redis connection. The important part is the name, we have two here, records and session.

For more info on what can be put under each name, see the extensive list on Connecting to Redis.

[ top, contents, module configuration, configuration sections ]

memory section

  "memory": {
    "redis": "session"
  }

memory_oc is an Ouroboros Coding module that handles the sessions for all services that run on the body_oc framework.

It needs to know which Redis connection it should use to store the session information. We have two from the redis section, records and session, and we are telling memory_oc to use the session one.

[ top, contents, module configuration, configuration sections ]

body section

  "body": {
    "rest": {
      "allowed": [ "mydomain.com" ],
      "default": {
        "domain": "localhost",
        "host": "0.0.0.0",
        "protocol": "http"
      },
      "services": {
        "brain": { "port": 8000, "workers": 10 },
        "mouth": { "port": 8001, "workers": 2 }
      }
    }
  }

body_oc is the rest / service framework that brain runs on top of. There's a lot of data here, but it's really only setting up two things.

body.rest.allowed

Represents the domains that can make cross origin requests to the RESTlike interface. This is mandatory if requests are being made from browsers, but in no way affects direct requests via curl / requests / postman / etc. In this case, we are allowing any pages across https://mydomain.com, this includes https://mydomain.com/some/page/, https://mydomain.com/other, and even https://admin.mydomain.com/.

To limit to a specific subdomain, change "allowed" to be more specific

      "allowed": [ "admin.mydomain.com" ]

this way https://admin.mydomain.com/ and https://bob.admin.mydomain.com/ work, but not https://mydomain.com/.

[ top, contents, module configuration, configuration sections, body section ]

body.rest.services

Second, in order to know how to both run and connect to body_oc services, we need to indicate, what protocol, domain, and port to use to connect, what interface they will respond to, and how many instances of each we can spin up.

In this instance we have two services, brain and mouth. Brain is available at http://localhost:8000 and will be running 10 threads that will be listening on ip 0.0.0.0, or internal only traffic. Mouth is available at http://localhost:8001 and will be running 2 threads that will also be listening on ip 0.0.0.0.

We are relying on the defaults to generate some of the data, and this is a very simplistic initial launch setup. As we launch more servers and spread the load, you might have the config on the brain server be something more like this where mouth is running on another server inside the network.

      "services": {
        "brain": {
          "domain": "localhost",
          "host": "192.168.0.1",
          "port": 80,
          "protocol": "http",
          "workers": 10
        },
        "mouth": {
          "domain": "mouth.mydomain",
          "port": 80,
          "protocol": "http"
        }
      }

...or like this, where it's running outside the network

        "mouth": {
          "domain": "mouth.mydomain.com",
          "port": 443,
          "protocol": "https"
        }

[ top, contents, module configuration, configuration sections, body section ]

brain section

  "brain": {
    "data": "../.data",
    "google": false,
    "internal": {
      "redis": "session",
      "salt": "K_B.c6iUMTeESC@Z",
      "ttl": 5
    },
    "mysql": "records",
    "portals": {
      "my_app": {
        "rights": {
          "my_service_permission": 15,
          "my_other_permission": 2
        },
        "ttl": 7884000
      }
    },
    "recaptcha": false,
    "redis": "records",
    "send_error_emails": true,
    "user_default_locale": "en-US",
    "verbose": false
  }

With the rest of the sections cleared up, hopefully the brain section is easier to digest.

brain.data

A directory where brain will store any files it needs. Things like the version file "brain.ver" which keeps track of what is installed and upgraded. We want this directory outside of the root of brain or the project it's in, in case that folder ever gets deleted.

[ top, contents, module configuration, configuration sections, brain section ]

brain.google

Google is used with the google/auth POST request, see Google Auth create.

[ top, contents, module configuration, configuration sections, brain section ]

brain.internal
    "internal": {
      "redis": "session",
      "salt": "K_B.c6iUMTeESC@Z",
      "ttl": 5
    }

The values are used to generate (access.generate_key) and verify (access.internal, access.internal_or_verify) internal requests between two services.

"redis" references one of the named connections from the redis configuration.

"salt" is a string used to encode a value.

"ttl" is the time to live in seconds before a message can no longer be trusted from the source.

[ top, contents, module configuration, configuration sections, brain section ]

brain.mysql

References the only named connection from the mysql configuration, records. This is the connection brain will use to store its tables.

[ top, contents, module configuration, configuration sections, brain section ]

brain.portals

Here each key of the object represents a possible portal in the system. For each portal listed, you can set

"rights" which are the default when a user is created or signed up, see rights

"ttl" the time to live in seconds for any session created by a user signing in.

[ top, contents, module configuration, configuration sections, brain section ]

brain.recaptcha

Recaptcha is used with the signup POST request, see Signup create.

[ top, contents, module configuration, configuration sections, brain section ]

brain.redis

References one of the two named connections from the redis configuration, records. This is where brain will stored cached versions of record data.

[ top, contents, module configuration, configuration sections, brain section ]

brain.send_error_emails

If true, error emails will be sent to the address in config.email.error_to whenever a request to brain causes an uncaught exception.

[ top, contents, module configuration, configuration sections, brain section ]

brain.user_default_locale

This is the default locale to set for created and signed up users if no locale was passed with the request.

[ top, contents, module configuration, configuration sections, brain section ]

brain.verbose

If true all requests to brain and the corresponding response will be printed to stdout.

[ top, contents, module configuration, configuration sections, brain section ]

Install Tables and Records

After installing the module into the project via pip, you will need to install the tables to your database.

foo@bar:~$ source myenv/bin/activate
(myenv) foo@bar:~$ pip install brain2_oc
(myenv) foo@bar:~$ brain install
Installing tables
Setting lastest version
Please enter details to give administrator access
E-mail address: me@mydomain.com
Password:
First name: Bob
Last name: Smith
User created
Permissions added
(myenv) foo@bar:~$ deactivate
foo@bar:~$ 

You can also run it directly without switching environments

foo@bar:~$ myenv/bin/pip install brain2_oc
foo@bar:~$ myenv/bin/brain install

Part of this process will be to store a file "brain.ver" in the brain.data directory with the current version of brain. This will be used by the upgrade process to know what, if any, scripts need to be run to update your data.

[ top, contents, module configuration ]

Upgrade Tables and/or Records

If you upgrade to a new version of brain2_oc be sure to run the upgrade script. This will ensure any data and tables are up to date with the new version.

foo@bar:~$ source myenv/bin/activate
(myenv) foo@bar:~$ brain upgrade
Already up to date
(myenv) foo@bar:~$ deactivate
foo@bar:~$ 

Alternatively

foo@bar:~$ myenv/bin/brain upgrade
Already up to date
foo@bar:~$ 

This process works by checking the "brain.ver" in the brain.data directory to see what, if any, scripts need to be run to upgrade tables and/or data. This is one reason why it's very important not to keep the brain.data directory inside your repository or some directory that might get wiped out.

Have no fear though, if the file does get wiped out, the next time the upgrade process is run you will be prompted to pick the current version you are on and the file will be re-created.

Take note though, the options you will be given will not correspond directly to every version of brain2_oc, only to the versions where data or tables changed in a way that the software couldn't support alone. When prompted, ALWAYS pick the version which is closest to, but under, the version of brain2_oc you have installed in your project. If you're on 8.1.0 and the choices are between 1.0.0 and 8.1.1, you pick 1.0.0.

[ top, contents, module configuration ]

Access Helper

The brain.helpers.access module contains requests to verify both internal and external connections requests.

access.generate_key

When sending a request to an endpoint as an internal request, assuming it allows it, we need the generate_key() method to generate a dict with the headers required for the request.

import body
from brain import access
response = body.create('brain', 'user', {
  'data': {
    'email': 'me@mydomain.com',
    'locale': 'en-CA',
    'first_name': 'Bob',
    'last_name': 'Smith',
    'portal': 'my_app',
    'url': 'https://mydomain.com/setup/{key}'
  }
}, access.generate_key())

If you need to add additional headers

import body
from brain import access

# Generate internal headers
headers = access.generate_key()

# Add 'my_header'
headers['my_header'] = 'my_header_value'

# Send request
response = body.create('brain', 'user', { """data""" }, headers)

[ top, contents, access helper ]

access.internal

When writing a request that allows internal connections, we can use the internal() method to verify the correct headers were sent.

from body import Service
from brain import access

class MyService(Service):
  def my_request_read(self, req: jobject) -> Response:

    # Check request is valid
    access.internal()

    # All good, return stuff
    return Response([ """ stuff to return """ ])

Nothing is passed and no checking is required when calling internal() If the request is not valid, a ResponseException will be raised which body will handle and return to the requester as an internal key, 1203 INTERNAL_KEY, error.

[ top, contents, access helper ]

access.internal_or_verify

Sometimes we might want to allow requests on both internal connections and based on session user permissions. In this case we can use the internal_or_verify() method. If successful, the method returns either the ID of the user in the session, or the SYSTEM_USER_ID, also available from access

internal_or_verify() has two arguments, session and permission which are the same as in access.verify below.

from body import Service
from brain import access

class MyService(Service):
  def my_request_read(self, req: jobject) -> Response:

    # Check request is valid
    sUserID = access.internal_or_verify(req.session, {
      'name': 'my_other_permission',
      'right' access.READ
    })

    # If this is an internal request
    if sUserID == access.SYSTEM_USER_ID:
      # do internal stuff
      pass

    # All good, return stuff
    return Response([ """ stuff to return """ ])

[ top, contents, access helper ]

access.verify

Verify is used to make sure the session user, aka, the currently signed in user, has the proper permissions to make a request. It's a shorthand for calling Verify read.

It has three arguments.

session is the current session making the request, in body requests this is always req.session.

permission is the "name", "right", and optional "id" to check against.

_return is an optional argument which defaults to False. If set to True, calling access.verify returns False on an error instead of raising a ResponseException.

from body import Service
from brain import access

class MyService(Service):
  def my_request_read(self, req: jobject) -> Response:

    # Check request is valid
    access.verify(req.session, {
      'name': 'my_other_permission',
      'right': access.READ
    })

    # All good, return stuff
    return Response([ """ stuff to return """ ])

permission can also be an array of multiple permissions to check against. If ANY are valid, verify returns True.

from body import Service
from brain import access

class MyService(Service):
  def my_request_read(self, req: jobject) -> Response:

    # Check request is valid
    access.verify(req.session, [{
      'name': 'my_other_permission',
      'right' access.DELETE # Fails
    }, {
      'name': 'my_service_permission',
      'right': access.READ # Succeeds, verify returns True
    }])

    # All good, return stuff
    return Response([ """ stuff to return """ ])

Unless the _return param is set to True, no checking of access.verify's return is required as ResponseException will be raised which body will handle and return to the requester as an insufficient rights, 1000 RIGHTS, error.

[ top, contents, access helper ]

Users Helper

The brain.helpers.users module contains methods to find out informatiom about users in the system.

users.details

Fetches details about one

from brain import users
users.details(
  _id = '18f85e33036d11f08878ea3e7aa7d94a',
  fields = [ 'email', 'first_name', 'last_name' ]
)
{
  "email": "me@mydomain.com",
  "first_name": "Bob",
  "last_name": "Smith"
}

...or many users

from brain import users
users.details(
  _id = [ '18f85e33036d11f08878ea3e7aa7d94a',
          '0905dba5042e11f0b65524a3c6f47776' ],
  fields = [ 'email', 'first_name', 'last_name' ],
  order = [ 'last_name', 'first_name' ]
)
{
  "0905dba5042e11f0b65524a3c6f47776": {
    "email": "johnnieb@gmail.com",
    "first_name": "John",
    "last_night": "Baker"
  },
  "18f85e33036d11f08878ea3e7aa7d94a": {
    "email": "me@mydomain.com",
    "first_name": "Bob",
    "last_name": "Smith"
  }
}

...or many users as an array

from brain import users
users.details(
  _id = [ '18f85e33036d11f08878ea3e7aa7d94a',
          '0905dba5042e11f0b65524a3c6f47776' ],
  fields = [ 'email', 'first_name', 'last_name' ],
  order = [ 'last_name', 'first_name' ],
  as_dict = False
)
[ {
  "email": "johnnieb@gmail.com",
  "first_name": "John",
  "last_night": "Baker"
}, {
  "email": "me@mydomain.com",
  "first_name": "Bob",
  "last_name": "Smith"
} ]

[ top, contents, users helper ]

users.exists

Validates if ID(s) point to valid user(s).

from brain import users

# Checking a single user
# returns True
users.exists('18f85e33036d11f08878ea3e7aa7d94a')
# returns False
users.exists('blahblah')

# Checking multiple users
# returns True
users.exists([
  '18f85e33036d11f08878ea3e7aa7d94a',
  '0905dba5042e11f0b65524a3c6f47776'
])
# returns False
users.exists([
  '0905dba5042e11f0b65524a3c6f47776',
  'blahblah'
])

[ top, contents, users helper ]

users.permissions

Fetches all, or by portal, the permissions for a specific user.

from brain import users
users.permissions('18f85e33036d11f08878ea3e7aa7d94a')
{
  "my_app": {
    "my_service_permission": {
      "012345679abc4defa0123456789abcde": 15
    },
    "my_other_permission": {
      "012345679abc4defa0123456789abcde": 2
    }
  },
  "my_other_portal": {
    "my_other_other": {
      "28069c9705ac11f0ba880c6af381aee5": 6
    }
  }
}

by portal

from brain import users
users.permissions('18f85e33036d11f08878ea3e7aa7d94a', 'my_app')
{
  "my_service_permission": {
    "012345679abc4defa0123456789abcde": 15
  },
  "my_other_permission": {
    "012345679abc4defa0123456789abcde": 2
  }
}

[ top, contents, users helper ]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brain2_oc-2.3.3.tar.gz (45.8 kB view details)

Uploaded Source

File details

Details for the file brain2_oc-2.3.3.tar.gz.

File metadata

  • Download URL: brain2_oc-2.3.3.tar.gz
  • Upload date:
  • Size: 45.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 colorama/0.4.4 importlib-metadata/4.6.4 keyring/23.5.0 pkginfo/1.8.2 readme-renderer/34.0 requests-toolbelt/0.9.1 requests/2.31.0 rfc3986/1.5.0 tqdm/4.65.0 urllib3/1.26.5 CPython/3.10.12

File hashes

Hashes for brain2_oc-2.3.3.tar.gz
Algorithm Hash digest
SHA256 6c3308827d4fdecf7a3de311b136a8a91d971d3eca8e88072d999671d2091e67
MD5 5d5252604504daacbc5abd9d5d1e391b
BLAKE2b-256 407048f70f5215770e9384bf170a32c69cabd7e3c6980647396263289016107f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page