MaIS Client Configuration

All uses of this package begin by instantiating a stanford.mais.client.MAISClient. But you should not instantiate the class directly. Instead, you should use one of the conveneice class methods for the Environment you plan on accessing.

What is an Environment?

MaIS operates multiple instances of its services, grouped into Environments. The Environment that everyone knows is PROD, the production environment accessible through URLs like workgroup.stanford.edu and accounts.stanford.edu. Changes made in PROD propagate to downstream systems like Active Directory and LDAP, and to third-party systems like Google Groups.

In addition to PROD, developers will often interact with the UAT Environment. UAT (short for “User Acceptance Testing”) is an environment with production-quality data, but which does not touch downstream production systems. For example, changes in UAT will not affect production LDAP, but they do propagate to a separate (UAT) LDAP environment.

UAT exists both for MaIS developers, and for clients: MaIS developers use UAT as a final testing area before a change ‘goes live’ in PROD. Clients use UAT as a way to visualize changes they plan on making in PROD. If they have a separate non-production infrastructure, some clients also use MaIS’ UAT platform as their non-production infrastructure’s ‘source of truth’, having users authenticate via UAT Stanford Login, use UAT LDAP, use UAT Workgroup Manager, etc.

Clients who are testing data related to the Pinnacle project also have access to the temporary UAT1 environment. This is an example of how environments can be ‘spun up’ as needed for larger projects. MaIS developers also create their own environments, as needed for their work. With proper configuration, this project (and the MAISClient) can access all of them.

The MAISClient

To get access to MaIS APIs, you will need a client certificate with permissions to the appropriate service and environment, and you will also need to instantiate a MAISClient using the appropriate class method.

For more information on how to obtain a client certificate, and get permission to use MaIS APIs, read Getting Started with the MaIS Web APIs.

The MAISClient class has three classmethods available, for connecting to the three Environments that most users will use:

  • prod() connects you to MaIS’ production APIs. This is what you will probably use in production code.

  • uat() connects you to MaIS’ UAT APIs. This is what you will probably use during testing and development.

  • uat1() connects you to MaIS’ special UAT1 API. This is what you will probably use during Pinnacle project work.

Warning

Not all MaIS applications and APIs are present in the UAT1 environment.

Here is a partial example (with parameters elided) showing how to access the different MAIS Environments:

# A MAISClient connected to the PROD Environment
client_prod = MAISClient.prod(...)

# A MAISClient connected to UAT
client_uat = MAISClient.uat(...)

# This Account API client is connected to PROD
aclient_prod = AccountClient(client_prod)

# This Account API client is connected to UAT
aclient_uat = AccountClient(client_uat)

The only difference between accessing PROD and accessing UAT, is in which MAISClient you instantiate.

Constructor Parameters

All of the MAISClient constructor class methods take the same parameters:

  • cert: A path to a client certificate

  • key: optional path to a client certificate key.

The parameters can be any “path-like” object, including a string or a pathlib.Path.

For example, here is a basic case, with client certificate and private key in separate files, at hard-coded paths:

client = MAISClient.prod(
  cert='/etc/myapp/cert.pem',
  key='/etc/myapp/key.pem'
)

If your path contains characters like ~ to refer to the user’s home directory, you should make the path into a pathlib.Path and call pathlib.Path.expanduser() before passing it to the constructor. You may also wish to subsequently call pathlib.Path.resolve() with strict=True to make the path absolute, and ensure it is valid. For example:

my_cert_and_key = pathlib.Path('~/symlink/app.pem')

try:
  cert = my_cert_and_key.expanduser().resolve(strict=True)
  client = MAISClient.prod(
    cert=cert,
  )
except OSError:
  print(f"Path `{my_cert_and_key}` not found")

The above example also shows how the MAISClient supports PEM files which contain the certficiate and its private key in a single file.

Tip

When the certificate and private key are in the same PEM file, the private key is placed after the certificate.

Warning

The PEM format supports storing private keys in encrypted format, where a passphrase is needed to decrypt the private key. This project does not support those types of private keys.

Timeouts

This package uses Python Requests to make API calls. By default, Requests does not have any explicit timeouts; timeouts are left up to the operating system. On Linux, for example, connections may take up to ~30 seconds to time out, and open connections may wait for hours without reading data.

For more control over timeouts, the MAISClient constructor has the timeout parameter, giving you two options for setting a timeout:

  1. To specify a single timeout (in seconds), used for both connecting and reading data, provide a single float number as the timeout.

    Important

    This does not combine the two timeouts into one, it just specifies the same number for both timeouts.

  2. To specify separate timeouts for connecting and reading, create an instance of the stanford.mais.client.Timeout class and use that.

Here is an example of both methods:

# Long connect and read timeouts of 20 seconds each
client1 = MAISClient.prod(
  cert='/etc/myapp/cert.pem',
  key='/etc/myapp/key.pem',
  timeout=20
)

# A short connect timeout (3.5 seconds); a long (20-second) read timeout
client2 = MAISClient.prod(
  cert='/etc/myapp/cert.pem',
  key='/etc/myapp/key.pem',
  timeout=stanford.mais.client.Timeout(
    connect=3.5,
    read=20,
  )
)

Custom Environments

If you are a MaIS developer, you might wish to use a MAISClient with an environment that is not PROD, UAT, or UAT1. Hello!

This is the only situation in which you would call the MAISClient constructor directly, instead of using one of its class methods (like prod() or uat()). You will need to create a _URLs instance, which is a typing.TypedDict containing one key for each MaIS API: The key is the API name and the value is the base URL.

dev_environment = stanford.mais.client._URLs(
  account='https://localhost:4000/accounts/',
)
credentials = pathlib.Path(os.environ['devcert'])

dev_client = MAISClient(
  cert=credentials.expanduser().resolve(strict=True),
  urls=dev_environment,
)
dev_account = AccountClient(dev_client)

Tip

You may also wish to set a custom Timeout.

Module Documentation

Here is detailed documentation for the stanford.mais.client module.

To begin using any of the MaIS API clients, you must first instantiate a MAISClient. This stores configuration common to all clients.

class stanford.mais.client.MAISClient(urls: _URLs, cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None)[source]

The MAISClient is the first thing you will instantiate when you want to interact with a MaIS API.

You probably do not want to call this directly. For convenience, you should one of the ready-made constructors, MAISClient.prod() and MAISClient.uat(). MAISClient.uat1() is also available for the temporary UAT1 environment.

This class is documented here only to be a reference for the parameters you will need to provide when using a convenience constructor (in particular, cert, key, and timeout).

urls: _URLs

A mapping of API to URL.

This mapping uses API names (like account) as keys, and the base URL as the value.

If you decided to not use the convenience constructors (MAISClient.prod(), etc.), then you will need to provide entries for each API you plan on using.

Raises:

TypeError – You did not provide a mapping.

cert: PathLike

The path to a TLS client certificate.

This may contain either a single TLS client certificate, or a TLS private key followed by a certificate. The combined format (key and cert in one file) was common in the old days; the former format (key and cert in separate files) is often preferred today.

The certificate (and key, if included) must be in PEM format (the text format that has “BEGIN” and “END” lines).

A test load will be made before the constructor completes.

Note

If you provide your own session, then this parameter is ignored, though the test will still be performed.

Warning

If you provide a non-absolute path, it may not be relative to a home directory (like ~), or to a variable (like %UserProfile%). If you have such a path, run it through pathlib.Path.resolve() first.

Raises:
  • FileNotFoundError – The file does not exist.

  • PermissionError – You do not have read permission on the file.

  • ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.

key: PathLike | None = None

The path to a TLS private key.

This must be the private key associated with the provided certificate, any must only be set if the private key is in a separate file from the certificate. If the private key and certificate are in the same file, then this must be set to None.

The file must be in PEM format (the text format that has “BEGIN” and “END” lines), and must contain a single key.

Warning

The private key must not be password-protected. Enabling support for this is covered in https://github.com/psf/requests/issues/2519.

A test load of the key and certificate will be made before the constructor completes.

Note

If you provide your own session, then this parameter is ignored. though the test will still be performed

Warning

If you provide a non-absolute path, it may not be relative to a home directory (like ~), or to a variable (like %UserProfile%). If you have such a path, run it through pathlib.Path.resolve() first.

Raises:
  • FileNotFoundError – The file does not exist.

  • PermissionError – We do not have read permission on the file.

  • ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.

session: Session

The Requests Session to use for API requests.

In most cases, you should not provide a Session during instance creation. The default behavior is to let the class constructor create the Session, using the cert, (optional) key, and default_timeout parameters. Some headers are also pre-configured.

If you provide your own Session, then you are responsible for configuring it, and the cert, key, and timeout parameters are ignored.

timeout: Timeout | float | None = None

The timeout to use for requests.

Requests does not support setting default timeouts, so we internally subclass requests.Session to implement a default timeout. This is the timeout that will be used.

In addition, to match what Requests supports, we accept None (to not set a specific timeout) and a single float (covering both timeouts).

There are two separate timeouts, a connect timeout and a read timeout. See the documentation of Timeout for more information.

classmethod prod(cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None) MAISClient[source]

Return a client configured to connect to production (PROD) APIs.

The returned client has all of the URLs pre-configured.

Note

A new client instance is created every time you call this. If you want to take advantage of caching, call this only once per thread.

Parameters:
Raises:
  • FileNotFoundError – The file does not exist.

  • PermissionError – We do not have read permission on the file.

  • ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.

classmethod uat(cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None) MAISClient[source]

Return a client configured to connect to production-track test (UAT) APIs.

The returned client has all of the URLs pre-configured.

Note

A new client instance is created every time you call this. If you want to take advantage of caching, call this only once per thread.

Parameters:
Raises:
  • FileNotFoundError – The file does not exist.

  • PermissionError – We do not have read permission on the file.

  • ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.

classmethod uat1(cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None) MAISClient[source]

Return a client configured to connect to connect to UAT1 APIs, used for Sequoia testing.

UAT1 is available for the Account, Authority (also known as “Privilege”) APIs, since those are the APIs that directly depend on data from HR.

If you have credentials that work for UAT, they will work for UAT1.

Note

UAT1 does have a Workgroup API, but it uses the XML-based 1.0 API. This Python package uses the JSON-based 2.0 API. Therefore, this Python package will not work with UAT1 at this time.

Note

A new client instance is created every time you call this. If you want to take advantage of caching, call this only once per thread.

Parameters:
Raises:
  • FileNotFoundError – The file does not exist.

  • PermissionError – We do not have read permission on the file.

  • ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.

Depending on your application, you may wish to set a non-default timeout. The Timeout class can help you do that.

class stanford.mais.client.Timeout(connect: float, read: float)[source]

Allows providing granular timeouts when making web requests.

This library uses Requests to make HTTPS calls. Requests has a peculiar way for dealing with timeouts. See the Requests documentation for more information on timeouts.

If you want to configure granular timeouts, you may do so using this class. The instance of this class is then passed to the MAISClient constructor.

connect: float

The connection timeout.

How long to wait for the TCP connection to open. This should be larger than a multiple of 3.0, due to TCP mechanics.

Warning

If connecting to a DNS name which has multiple IPs, each IP will be tried, and so the total timeout will be this value multiplied by the number of IPs.

read: float

The read timeout.

How long to wait for data, once the request has been sent off. This timeout resets whenever a single byte of data is received from the server.

Finally, if you plan on instantiating a MAISClient that points to an Environment that does not have a convenience function (like prod() or uat()), you will need to provide a _URLs.

class stanford.mais.client._URLs[source]

The different services that MaIS provides, and their API endpoints.

Note

Just because a service is listed here, does not mean this package supports it.