## Thought it looks better without the breadcrumbs bar, and kinda redunant since we have the sidebar nav
Origen v2
:

Table Of Contents

Contents

Previous topic

Using a Plugin

Next topic

Developers

Utilities

Origen comes with some utilities to integrate commonly-used tasks that occur in industry or larger-scale use cases with your Origen apps.

Users

Origen provides a representation of users to ease interactions with articles like passwords, home directories, and email addresses.

Just by booting up Origen, the current user is always available at origen.current_user(). The user’s ID, which is discerned from the OS, can be read using the _origen.users.User.id() method.

Additional users can be added with origen.users.add('<id>'). Given any ID, this creates a new User and stores it in the dict-like container, Users, in a way which both the frontend and backend can access and query fields. You can see all added users with origen.users.users.

A user comes with a number of fields that can be set and queried at will:

The username and display_name fields act a bit differently. If they are unset, a backup is returned instead. See the API for details:

Passwords work in much the same way with the caveat of an unset for the current user triggers a dialog prompting the user to enter their password. Non-current users will need to explicitly set passwords with the setter method, password=.

Users can be customized by the Origen config. The below sections will go through some of these while the sample configuration in the repository contains a working example.

Default Configuration

The next sections will get into customizations for more advanced features but for simple applications, the default configuration may be enough.

The default configuration supports all of the same functions above. Simply omitting the configuration values user__data_lookup_hierarchy and user__datasets, you will get a default dataset of __origen__default__ and a hierarchy of ["__origen__default_"]. Exactly what this means will be covered in the next section but, for now, just note these as the default values.

For other semi-default cases, see further down.

Datasets

For cases where Origen acts not just as a pattern or program generation tool but as a more full-fledged application or tool distribution system, a single user may be attached to multiple systems.

Consider an application which builds a workspace from two independent systems: an internal Bitbucket system, and the external Github system, with the intention of building a fully-functioning development workspace, requiring proper credentials for both systems simultaneously.

For this, two sets of credentials are needed, as the BitBucket credentials need not match that same user’s Github credentials. Datasets allow for a single user to set and query fields independent of each other. For example:

origen.current_user().datasets['git'].password = "git"
origen.current_user().datasets['bitbucket'].password = "bb"
origen.current_user().datasets['git'].password
    #=> "git"
origen.current_user().datasets['bitbucket'].password
    #=> "bb"

Using the Origen config, we can indicate that additional datasets are needed:

[user__datasets]
[user__datasets.git]
[user__datasets.bitbucket]

These are then available on the dict-like container datasets and our example application can interact with these two systems as the same user with two different sets of credentials.

from somewhere import access_git, access_bitbucket

git_dataset = origen.current_user().datasets['git']
bb_dataset = origen.current_user().datasets['bitbucket']

access_git(git_dataset.username, git_dataset.password)
access_git(bb_dataset.username, bb_dataset.password)

Each dataset can be configured independently from the Origen config. This will be most applicable when handling data integration.

When a user field is accessed on any User, instead of a dataset directly, the data_lookup_hierarchy is followed to actually look up what is returned. This hierarchy will go dataset-by-dataset until a “non-None” value is found. If None is returned anyway, then no dataset provided the given field. This hierarchy can be set in the Origen config:

user__data_lookup_hierarchy = ["git", "bitbucket"]
# Set the 'first_name' fields for both datasets
origen.current_user().datasets['git'].first_name = "Git"
origen.current_user().datasets['bitbucket'].first_name = "BB"

# Get the first name. Dataset "git" will be returned
origen.current_user().first_name
    #=> "Git"

# Clear Git's first name
# As the "Git" dataset no longer provides a first name, the "BB" dataset will be used.
origen.current_user().datasets['git'].first_name = None
origen.current_user().first_name
    #=> "BB"

This hierarchy can be set programmatically, per-User:

origen.current_user().datasets['git'].first_name = "Git"
origen.current_user().datasets['bitbucket'].first_name = "BB"

origen.current_user().datasets['git'].first_name
    #=> "Git"
origen.current_user().datasets['bitbucket'].first_name
    #=> "BB"

origen.current_user().first_name
    #=> "Git"

origen.current_user().data_lookup_hierarchy = ["Bitbucket", "Git"]

origen.current_user().first_name
    #=> "BB"

Some fields, however, are exempt from this scheme, the username and password among them, and will stop at the top_datakey - which is the first value in the hierarchy and of the highest priority.

origen.current_user().datasets['git'].password = "git"
origen.current_user().datasets['bitbucket'].password = "bb"

origen.current_user().password
    #=> "git"

origen.current_user().datasets['git'].password
    #=> "git"
origen.current_user().datasets['bitbucket'].password
    #=> "bb"

origen.current_user().clear_cached_password("git")
origen.current_user().password
    #=> <- Begin Password Dialog ->
    #=> <- Does not query bitbucket dataset ->

Not all defined datasets need be in the hierarchy and a hierarchy that only include a single value will essentially alias all the fields on the User to a single dataset.

Furthermore, an empty hierarchy will not allow any field accesses on the User - all accesses must be explicitly done though the Dataset.

Data Integration

Users can be integrated with supported data-sources, which will populate the fields for a dataset behind the scenes. Currently, the only supported sources are the Origen’s LDAP and the Git Configuration, but more may be added in the future.

All data integration in done via the Origen config. The data_source option denotes what other options are available and how they are used.

LDAP Integration

These options only pertain to integrating an existing Origen’s LDAP configuration with a user dataset. See the Origen’s LDAP section for setting up the LDAP itself.

Integrating a LDAP is done per-dataset where the ldap’s name and various lookup parameters are given. A dataset_mappings table will map the LDAP’s attribute to the available fields.

# Add user datasets
[user__datasets]

# Add two blank datasets
[user__datasets.git]
[user__datasets.bitbucket]

# Add a dataset with LDAP integration
[user__datasets.ldap]
data_source = "ldap"        # Indicates LDAP data source
data_lookup = "ldap"        # The LDAP name.
                            # The LDAP itself is configured elsewhere
data_id = "uid"             # When searching the LDAP, indicates what attribute
                            # should be used during lookup.
data_service_user = "ldap"  # Indicates if a service user should be used to
                            # search the LDAP. The service users are also
                            # configured elsewhere.
try_password = true         # When a password is retrieved, attempted to validate it
                            # against the LDAP (e.g., attempt to bind with the "data_id"
                            # and given password.
auto_populate = false       # Indicate if the LDAP should populate
                            # the user field at initialization.

The Users tests contains a setup and some tests against a freely available LDAP and can be used as an example and a reference.

Git Configuration

If Git is installed and accessible, a dataset can be populated from the Git Configuration. Currently, the display_name and email are the only values queried.

[user__datasets.git]
data_source = "git"

More On Passwords

Password Caching

By default, users who have had their passwords set and validated will have them stored in the Linux Keyring (or the Windows Credential Manager) for future retrieval.

This can be explicitly set in the Origen config by setting the user__password_cache_option to either true or keyring.

A second option is to store the passwords in the session store. Passwords stored here are encrypted using Origen’s default encryption key and default encryption nonce, so this is really just to avoid plaintext password storage as opposed to an actual security mechanism, but these too can be overridden by the Origen config.

# Allows passwords to be stored in the user's session store
user__cache_passwords = "session"

# Allows custom encryption keys used by passwords only
# These must conform to AES-256 GCM standards
password_encryption_key = "..."
password_encryption_nonce = "..."

Regardless of the caching mechanism used, passwords stored will persists not just across invocations but across applications, as well as being available in global invocations.

In addition to the password dialog and the password= method shown previously, passwords for the current user can be set and cleared on the command line with the credentials command:

origen.exe-credentials
Set or clear user credentials

USAGE:
    origen.exe credentials [FLAGS] [SUBCOMMAND]

FLAGS:
    -h, --help    Prints help information
    -v            Terminal verbosity level e.g. -v, -vv, -vvv

SUBCOMMANDS:
    clear    Clear the user's password
    help     Prints this message or the help of the given subcommand(s)
    set      Set the current user's password

Password caching can also be disabled entirely by setting user__password_cache_option to either false or none.

Password “Reasons”

Returning to the example from Datasets momentarily, recall that password datasets can be retrieved on a per-dataset basis. There is an alternative though: passwords can be retrieved for the given reason, which will attempt to match an arbitrary string with its corresponding dataset.

These “reasons” are set in the Origen config and are retrieved by passing the reason into the password_for method. Without other options, this will raise an exception if the password reason is not found. dataset_for can query if a dataset matches the given reason.

[user__password_reasons]
"just because" = "git"
origen.current_user().default_dataset
    #=> "bitbucket"
origen.current_user().dataset["git"].password = "git_pw"
origen.current_user().dataset["bitbucket"].password = "bb_pw"

origen.current_user().password_for("just because")
    #=> "git_pw"

origen.current_user().password_for("no reason")
    #=> Error

A default dataset option will return the password for that dataset in the event the reason is unmatched. The special value None can also be given to return the global default dataset:

origen.current_user().password_for("no reason", default: "git")
    #=> "git_pw"

origen.current_user().password_for("no reason", default: None)
    #=> "bb_pw"

Password Validation

If a data_source is available, passwords can be validated against the given system. By default, passwords will always be validated when the setup allows but this can be disabled on a per-dataset basis with the try_password key.

Service Users

Service users, or possibly known as functional accounts, are accounts with a dedicated purpose, usually to interact with a system on other’s behalf. These users can be added in the Origen config:

# Create a service account 'service' with username 'serv' and password 'pass'
[service_users]
[service_users.service]
username = "serv"
password = "pass"

Other Configuration Cases

When the configuration is omitted, Origen will provide a default setup. However, other semi-default cases arise when particular conditions are met:

  • If no datasets are given, but a hierarchy is provided, an error message is printed and the default user config is forced.

  • Likewise, invalid hierarchies (such as duplicates or missing entries), preserves the datasets, but clears the hierarchy (e.g., user__data_lookup_hierarchy = [].

  • If a single dataset is provided but no hierarchy is given, the hierarchy is automatically set to that dataset.

  • However, if multiple datasets are given but not a hierarchy, then the hierarchy will be empty.

See Also

LDAP

Origen includes a wrapper for the Lightweight Directory Access Protocol, or LDAP Wiki, an interface common in corporate environments for storing user data.

LDAP instances are added via Origen config. A single LDAP only has a few parameters:

# Denote that there are LDAPs
[ldaps]

# A single LDAP configuration, with name "forumsys"
[ldaps.forumsys]
# Required server and port location, combined into one URL
server = "ldap://ldap.forumsys.com:389"

# Required base DN for all operations, including binding
base = "dc=example,dc=com"

# Optional auth scheme. Currently, only "simple_bind" exists, but others
# may be added in the future.
auth = "simple_bind"

# Optional service user account to use for binding and searching.
# If none is given, the 'username' and 'password' parameters will be used.
service_user = "ldap_account"

# Username and password to use for binding, if the service user is not given.
# If a service user is given, these are ignored.
username = "u"
password = "p"

Note: the above is a configuration for a free LDAP server and should work for testing or debug. See the LDAP tests for example interactions with this system.

Added LDAPs are available as origen.ldaps, a dict-like container:

origen.ldaps.keys
    #=> ['forumsys']

origen.ldaps['forumsys']
    #=> _origen.utility.ldap.LDAP

# Bind (connect to, with the service user or username/password, depending on which was given)
origen.ldaps['forumsys'].bind()
    #=> True # if successful

Common Methods

The LDAP wrapper has two main purposes: general searches and validating user’s credentials.

Searching can be done using the search method. This takes a filters and an attribute list and spits out the resulting query. For simpler searches, where the filters is expected to return exactly one or zero entries, you can use search_single_filter to get a friendlier return value. If more than one entry is returned then an error is raised.

The validate_credentials method will check that the given username and password validates against the LDAP. The state of the LDAP itself is unchanged.

Implementation note: this method looks strictly for error code 49, LDAP invalid credential. An exception will be raised for other error codes.

Scope Of Origen’s LDAP

Currently, the LDAP wrapper does not support modification functions and there are no plans to add these by the core team at this team.

For authentication, only “simple_bind”, by providing a username and password, is supported. More auth schemes can be added as needed but the core team does not currently have a means to validate them, so they are omitted. If additional auth schemes are needed, please open a ticket to start the discussion.

LDAP Resources

For more information on Origen’s LDAP, see the resources below:

Mailer

A simple command-line interface is also available:

origen.exe-mailer
Command-line-interface to Origen's mailer for quick emailing or shell-scripting

USAGE:
    origen.exe mailer [FLAGS] [SUBCOMMAND]

FLAGS:
    -h, --help    Prints help information
    -v            Terminal verbosity level e.g. -v, -vv, -vvv

SUBCOMMANDS:
    help    Prints this message or the help of the given subcommand(s)
    send    Quickly send an email
    test    Send a test email

Session Storage

Some features or plugins cache simple data pieces regarding the current user, workspace configuration, environment, or other aspects - password caching being one - where the data should persists across invocations. Origen’s session store provides an interface for such a task.

The current session is accessed through origen.app.session and has two key functions: store and get. As their names suggest, store will put data into the session, storing it for future retrievals while get will retrieve previously stored data.

origen.app.session.get("val")
    #=> None
origen.app.session.store("val", 1)
origen.app.session.get("val")
    #=> 1
origen.app.session.store("val 2", 2)
origen.app.session.get("val 2")
    #=> 2

delete will remove an item from the session entirely, returning the deleted value. However, this can also be achieved by storing a None value, but without getting the value back:

origen.app.session.get("val")
    #=> 1
origen.app.session.get("val 2")
    #=> 2

origen.app.session.delete("val")
    #=> 1
origen.app.session.get("val")
    #=> None

origen.app.session.store("val 2", None)
origen.app.session.get("val 2")
    #=> None

Session Scopes

Previously, we used origen.app.session() to get handle on the application’s session. As the name would suggest, this session is application specific. Navigating to a different Origen application’s workspace will not carry any of the previous application’s session data over.

For session data that should exists for a given user across all of their applications, or even outside of an application, the user session can be used. Password caching is one such item stored in the user’s session, as opposed to the application’s. Other than scope, this session store behaves identically to the application session.

This session is accessed as origen.session_store.user_session().

Session Namespaces

Using session() without any arguments yields generic session storage for the application. For organizational purposes, and to ensure that different features or plugins do not inadvertently step on each other, an optional str argument will grab an entirely disjoint session under that name.

origen.session_store.app_session().store("test", 1)
origen.session_store.app_session("alt").store("test", 2)

origen.session_store.app_session().get("test")
    #=> 1
origen.session_store.app_session("alt").get("test")
    #=> 2

This same feature is available for user sessions as well.

For plugins, the instance itself can be passed to yield its dedicated session. Note however that this is only a syntactic difference and yields the same session as if plugin’s name was used instead.

pl = origen.plugins("python_plugin")

# Retrieve a plugins app session
# These two are equivalent
origen.session_store.app_session(pl)
origen.session_store.app_session(pl.name)

# Same is true for user sessions
origen.session_store.user_session(pl)
origen.session_store.user_session(pl.name)

As a syntactic shortcut, a plugin’s session can also be retrieved from the plugin itself:

pl.session
    #=> origen.session_store.app_session(pl)

pl.user_session
    #=> origen.session_store.user_session(pl)

Data Serialization

Almost any Python object can be stored in the session. Standard objects which could also be used by the Rust backend, such as strings, numbers, booleans, or lists of those types, are stored directly. Any other objects, such as custom classes, are serialized using pickle.

You can opt to store and get data through your own serialization mechanism. The method store_serialized will bypass any serialization or data type inference occurring in the backend and simply store the given bytes directly. When it is retrieved, via the standard get method, the bytes are retrieved. See the session store tests for an example of storing via marshal.

Session Store Resources