# account-provisioning-for-google-apps
**Repository Path**: mirrors_google/account-provisioning-for-google-apps
## Basic Information
- **Project Name**: account-provisioning-for-google-apps
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-08-08
- **Last Updated**: 2025-12-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Account provisioning for Google Apps
Account provisioning for Google Apps is an open source API to:
* **Generate available usernames** in your Google Apps domain
* **Create Google Apps accounts** in your domain
It can be used in a website where users create their own accounts...
![self account provisioning demo][selfGif] |
-------------
a script that creates accounts in bulk...
![bulk account provisioning demo][bulkGif] |
-------------
or via a CSV input...
![csv input][csv] |
-------------
Usernames are generated automatically via configurable patterns. Sample images are taken from the included demos. Give them a try [here](#quick-start)!
This API can be installed as a RESTful service (to be invoked from almost any programming language and platform) or as a Java library.
### Who should use it
Google Apps deployments where new usernames need to be created.
Deployments that need to sync existing usernames can use [Google Apps Directory Sync] (https://support.google.com/a/answer/106368?hl=en) or [Google Apps School Directory Sync] (https://support.google.com/a/answer/6027781?hl=en).
### Why you should use it
* **Username cache:** It caches all your Google Apps accounts, which makes it fast (minimum calls to Google servers) and less likely to hit [Directory API calls/day limits] (https://developers.google.com/admin-sdk/directory/v1/limits)
* **Custom user fields:** Usernames are generated from the user's first name, last name and a set of (optional) custom fields, i.e. second last name, student ID, department, nickname, etc.
* **Locked suggestions:** Suggested usernames will remain locked (unavailable to other users) until they expire or are explicitly unlocked.
* **Backed by Google Apps Admin SDK:** Uses the Google Apps [AdminSDK Directory API] (https://developers.google.com/admin-sdk/directory/)
* **SSL support:** All REST calls can be encrypted using SSL
* **Any programming language:** You can choose any programming language and platform that supports REST calls. Almost any language and platform do.
### What methods does this API offer
* **`suggest`**: returns a list of username suggestions based on first name, last name and a set of custom fields.
* **`create`**: creates a Google Apps account.
* **`select`**: unlocks username suggestions that will no longer be used.
> See the [API Overview](#api-overview-and-sample-code) section to learn more about how to invoke these methods.
# Quick start
*Ready? Try the demos!*
This API needs Java, but don't worry you don't need to develop your client in Java. You can use any language and platform you like (Python, JavaScript, PHP, C#, ObjectiveC, Go, etc.) as long as it can do [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) calls.
> Included demos are built in JavaScript.
### 1. Check your Java version
In the terminal run:
```bash
java -version
```
If you see: `java version "1.7.X"` or a newer version you are ready to go. If not, [install Java 7 or a newer verion](https://java.com/en/download/help/download_options.xml).
### 2. Set your Google Apps domain configuration
Follow the steps in the [Google Apps domain configuration guide][configGuide] to configure your domain to work with this API.
### 3. Start the RESTful API service
Move the `config.properties` and the `p12` file (created in the previoius step) to the `bin/` folder.
In the terminal, run:
```bash
java -jar appsProvisioning-0.0.1.jar -rest -port 8080
```
> This will start the RESTful API service on port 8080. If you already have a server running on port 8080, change it to another port, e.g. 8888.
### 4. Open a demo
Awesome! You can now start getting usernames suggestions and creating Google Apps accounts in your domain.
> See the Note below if you used a different port from 8080
Open any of the `index.html` demos under the `demos/` folder:
Demo | Sample
------------- | -------------
self-provisioning-demo
Each user selects and creates their own account | ![self account provisioning demo][selfGif]
bulk-provisioning-demo
All accounts are created in bulk | ![bulk account provisioning demo][bulkGif]
csv-provisioning-demo
All accounts are created in bulk from a CSV input | ![csv demo][csv]
**Note:** If you used a different port from 8080, open first the JavaScript `.js` file inside the demo folder and update
```javascript
var API_HOST = 'http://localhost:8080';
```
to point to the right port. For example:
```javascript
var API_HOST = 'http://localhost:8888';
```
Both demos will initially show the configuration parameters that are relevant to the client. For example:
![demo info][demoInfo]
> This screen will disappear after a couple of seconds.
You can change these parameters in the `config.properties` file and use this screen to verify the current configuration. Follow the next step to see how.
> A quick way of testing your server is by invoking the `suggest` method via the GET service http://localhost:8080/rest/suggest?firstname=john&lastname=smith
### 5. Play with the configuration
Now that you have the demos up and running is a great time to learn how usernames are generated.
1. Open the `config.properties` file and look for the `accounts.UsernameGeneration.patterns` property.
2. Replace its value with the following one:
```properties
accounts.UsernameGeneration.patterns=[C1_firstname].[lastname][custom1],[C3_lastname]_[custom1],[C1_custom1][firstname],[firstname][C1_lastname]_[custom1],[lastname][custom1]
```
Now kill the Java process (`Ctrl+C` or `Cmd+C`) in the terminal and start the RESTful API service again ([step 3](#3-start-the-restful-api-service)). This will load the new `patterns` configuration, which will result in different usernames being generated.
Open the `self-provisioning-demo\index.html` demo and notice how the generated usernames are now different. They now follow the new patterns set in the config file. The [configuration section](#accountsusernamegenerationpatterns) explains how to patterns work.
Next, you can try changing other `accounts.UsernameGeneration` properties. For example, updating the following properties:
```properties
accounts.UsernameGeneration.numberOfSuggestions=5
accounts.UsernameGeneration.suggestedUsernamesTimeout=10
```
This will result in:
* 5 usernames being suggested (instead of 3)
* usernames will expire after 10 seconds (instead of in 2 minutes)
### Next steps
* To learn more about other configuration properties (e.g. how to enable SSL in your server), take a look at the [Configuration properties][config] section.
* To start changing the client side code, take a look at the [API overview](#api-overview-and-sample-code) section.
* To modify the server code side, take a look at the [Contributing](#contributing) section.
* If you have any questions or feedback, please let us know in the [forum][forum]!
## Index
* [System overview](#system-overview)
* [API overview and sample code](#api-overview-and-sample-code)
* [Configuration properties][config]
* [Building][building]
* [Feedback](#feedback)
* [License](#license)
* [Contributing](#contributing)
# System overview
This API is developed in Java and can be invoked from any language and platform that can do REST API calls. The username cache is an [H2](http://www.h2database.com/) database, so you will see a `usernames(*).mv.db` file when running the API.
![use cases diagram][useCasesDiagram]
> Only usernames are cached, no names or other user's data is ever stored. The [H2 Console Application](http://www.h2database.com/html/quickstart.html#h2_console) can be used to inspect the cache.
The cache can be disabled with the [`cachedUsernames`](#accountsusernamegenerationcachedusernames) property. When disabled it will do Admin SDK API calls. When enabled, the cache will refresh periodically (see the [`cacheExpirationHours`](#accountsusernamegenerationcacheexpirationhours) property).
# API overview and sample code
### `suggest` method
**Description:** Method that suggests available usernames in a domain. Uses the configuration file (see [configuration][config]) to determine:
* Number of suggested usernames: [`numberofsuggestions`](#accountsusernamegenerationnumberofsuggestions)
* Patterns used to generates usernames: [`patterns`](#accountsusernamegenerationpatterns)
* Google Apps Domain: [`domain`](#apisgoogleapisdomain)
**Note:** All suggested usernames will remain locked until they expire (see [`suggestedUsernamesTimeout`](#accountsusernamegenerationsuggestedusernamestimeout)) or the [`select`](#select-method) method is called.
| REST API | Java API |
------------ | ------------- | ----------------
**Method** | `rest/suggest` | `apps.provisioning.server.account.UsernameManager.suggest`
**Parameters** | JSON map with the following fields:
- `firstname` the user's first name
- `lastname` the user's last name
The JSON map might include customizable fields. For example:- `secondLastname`
- `nickname`
| `userData`: A `java.util.HashMap`with the following fields: - `firstname` the user's first name
- `lastname` the user's last name
The HashMap might include customizable fields. For example:- `secondLastname`
- `nickname`
**Returns** | In case of success, it returns a JSON serialized array with username suggestions, in case of error it returns a JSON serialized map with the `"errorMessage"` index explaining the error. | A `java.util.ArrayList` of suggestions. Throws an `Exception` in case of an error.
#### Sample code for `suggest`
##### REST API
```javascript
var url = 'http://localhost:8080/rest/suggest';
var parameters = '{' +
'"firstname": "Carlos",' +
'"lastname": "Alvarez",' +
'"secondLastname": "Martinez"' +
'}';
var xhr = new XMLHttpRequest();
xhr.onload = function() {
alert(this.responseText);
};
xhr.open('POST', url, true);
xhr.send(parameters);
```
##### Java API
```java
HashMap userData = new HashMap();
userData.put("firstname", "Carlos");
userData.put("lastname", "Álvarez");
userData.put("secondLastname", "Martinez");
ProvisioningApp provisioningApp = ProvisioningApp.getInstance();
provisioningApp.initApp();
UsernameManager usernameManager = provisioningApp.getUsernameManager();
ArrayList suggestions = usernameManager.suggest(userData);
```
**Result**
```javascript
["carlos.alvarez","carlosalvarez","c.alvarez_martinez"]
```
**Note:**
The following `config.properties` (see [configuration][config]) was used for this example:
```properties
...
patterns = [firstname].[lastname], [firstname][lastname], [C1_firstname].[lastname]_[secondLastname]
numberOfSuggestions = 3
...
```
## `select` method
**Description:** Selects the given username from the given suggestions. This will unlock all the suggestions, except the selected one (if any).
| REST API | Java API |
------------ | ------------- | ----------------
**Method** | `rest/select` | `apps.provisioning.server.account.UsernameManager.select`
**Parameters** | JSON map with the following fields: - `username` the selected username
- `suggestions` a list of suggestions
| - `suggestions` an `ArrayList` of suggested usernames
- `selectedUsername` the selected username
**Returns** | In case of success, it returns a JSON serialized array with username suggestions, in case of error it returns a JSON serialized map with the `"errorMessage"` index explaining the error. | `void`
Throws an `Exception` if an error occurs.
#### Sample code for `select`
##### REST API
```javascript
var url = 'http://localhost:8080/rest/select';
var parameters = '{"username":"carlos.alvarez", "suggestions":["carlos.alvarez","carlosalvarez","c.alvarez"]}';
var xhr = new XMLHttpRequest();
xhr.onload = function() {
alert(this.responseText);
};
xhr.open('POST', url, true);
xhr.send(parameters);
```
**Result**
```javascript
{"message":"User selected successfully."}
```
##### Java API
```java
String selectedUsername = "carlos.alvarez";
ArrayList suggestions = new ArrayList();
suggestions.add("carlos.alvarez");
suggestions.add("carlosalvarez");
suggestions.add("c.alvarez");
ProvisioningApp provisioningApp = ProvisioningApp.getInstance();
provisioningApp.initApp();
UsernameManager usernameManager = provisioningApp.getUsernameManager();
usernameManager.select(suggestions, selectedUsername);
```
## `create` method
**Description:** Creates a Google Apps account in the provided Google Apps Domain (see [`domain`](#apisgoogleapisdomain)).
| REST API | Java API |
------------ | ------------- | ----------------
**Method** | `rest/create` | `apps.provisioning.server.account.UsernameManager.create`
**Parameters** | JSON map with the following fields: - `username` account's username
- `firstname` user's first name
- `lastname` user's last name
- `password` account's password
| - `username` account's username
- `firstname` user's first name
- `lastname` user's last name
- `password` account's password
**Returns** | In case of success, it returns a JSON serialized array with username suggestions, in case of error it returns a JSON serialized map with the `"errorMessage"` index explaining the error. | `void`
Throws an `Exception` if an error occurs.
> Username and password fields must comply with the [Google Apps Name and password guidelines] (https://support.google.com/a/answer/33386?hl=en)
#### Sample code for `create`
##### REST API
```javascript
var url = 'http://localhost:8080/rest/create';
var parameters = '{"username":"carlos.alvarez", "firstname":"Carlos", "lastname":"Alvarez", "password":"12345678"}';
var xhr = new XMLHttpRequest();
xhr.onload = function() {
alert(this.responseText);
};
xhr.open('POST', url, true);
xhr.send(parameters);
```
**Result**
```javascript
{"message":"User created successfully."}
```
##### Java API
```java
String username = "carlos.alvarez";
String firstname = "Carlos";
String lastname = "Alvarez";
String password = "12345678";
ProvisioningApp provisioningApp = ProvisioningApp.getInstance();
provisioningApp.initApp();
UsernameManager usernameManager = provisioningApp.getUsernameManager();
usernameManager.create(username, firstname, lastname, password);
```
## API limits
Account provisioning for Google Apps follows the same [AdminSDK Directory API limits] (https://developers.google.com/admin-sdk/directory/v1/limits). Each call to `create`, `select` and `suggest` consumes a different number of Directory API calls:
- `create`: 1 API call
- `select`: 0 API calls
- `suggest`:
- cache enabled ([`cachedUsernames=YES`](#accountsusernamegenerationcachedusernames)): 0 API calls
- cache disabled ([`cachedUsernames=NO`](#accountsusernamegenerationcachedusernames)): number of API calls is equal or larger than the [`numberOfSuggestions`](#accountsusernamegenerationnumberofsuggestions) property
# Configuration properties
The configuration is set in the `config.properties` file. Configuration properties are divided in four categories:
1. [Username generation properties](#username-generation-properties): use the property prefix `accounts.UsernameGeneration.`
2. [Google API properties](#google-api-properties): use the property prefix `apis.GoogleAPIs.`
3. [Cache location properties](#cache-location-properties): use the property prefix `db.h2.`
4. [SSL properties](#ssl-properties): use the property prefix `security.ssl.`
## Username generation properties
##### `accounts.UsernameGeneration.cachedUsernames`
**Description**: A username cache can be used to check if a username already exists. This prevents reaching AdminSDK API calls/day limit. If `cachedUsernames` is set to `YES` username availability will be checked against the cache. If set to `NO` it will be checked against the Google Directory (using a Directory API call).
**Possible values**: `YES` and `NO`
-------------
##### `accounts.UsernameGeneration.cacheExpirationHours`
**Description**: Defines the expiration time in hours of the usernames cache. After expiration, the application refreshes the username cache. For reference, refreshing an account with 1 million users takes approximately 35 minutes.
**Possible values**: Integers larger or equal to 1
**Default**: `24`
-------------
##### `accounts.UsernameGeneration.numberOfSuggestions`
**Description**: The number of username suggestions to be returned for each call to `suggest`.
**Possible values**: Integer between 1 and 10 (inclusive)
**Default**: `3`
-------------
##### `accounts.UsernameGeneration.suggestedUsernamesTimeout`
**Description**: The amount of time (in seconds) that suggested usernames will remain locked (unavailable to another client).
**Possible values**: Integer greater than 0.
**Default**: `120` (2 minutes)
-------------
##### `accounts.UsernameGeneration.patterns`
**Description**: A pattern is something that looks like `[firstname][lastname]`. This pattern indicates the API that we want to generate a username with *"the firstname followed by the lastname"*. Now, if that username happens to be taken the API will need another pattern. Therefore, a list of multiple patterns is recommended. For example:
```properties
accounts.UsernameGeneration.patterns=[firstname][lastname],[lastname][firstname]
```
This will first try to generate a username with *"the firstname followed by the lastname"* if that is taken then it will try to generate a username with *"the lastname followed by the firstname"*.
*So far so good?*
Great. Now, say we want to generate a username with *"the nickname followed by the lastname"* of a person. We'd simply do:
```properties
accounts.UsernameGeneration.patterns=[nickname][lastname]
```
*What's `[nickname]` you might ask?*
Well, `nickname` is a field that should be passed to the [`suggest`](#suggest-method) method. For example:
```javascript
var parameters = '{' +
'"firstname": "Jonathan",' +
'"lastname": "Bravo",' +
'"nickname": "Jonny"' +
'}';
```
This will generate the username `jonnybravo`.
**NOTE:**
* It is not mandatory to pass `nickname` (or any custom field) for all users. If `nickname` is not provided for a user the API will skip that pattern and move on to the next one.
* `firstname` and `lastname` are mandatory. Even if the pattern doesn't make use of them.
#### Taking the first char(acters) of a field
Now say that for someone named *John Smith* we'd like to generate a username `jsmith`. We'd do it with the following pattern:
```properties
[C1_firstname][lastname]
```
**C1** indicates that the API should take **the first character** of **firstname**. If we'd like to take the first **two** characters then we'd do:
```properties
[C2_firstname][lastname]
```
This same method applies for custom fields. For example, if we wanted to split the first character of the `nickname` we'd do:
```properties
[C1_nickname]
```
#### Adding a counter to a pattern
Now, say we have so many people named "John Smith" in a school district that we ran out of patterns. We could then define a pattern that adds a number at the end of the username:
```properties
[firstname][lastname][#]
```
**[#]** indicates that a number will be added to the username. For example: `johnsmith3`. This numeric value will start at 1 and continue adding until a username is available.
**Note:** The use of **[#]** should be the last resort as this counter can become hard to remember, e.g. `johnsmith3816`. A list of multiple patterns without **[#]** is encouraged before using a pattern with **[#]**.
#### Adding separators to usernames
A common practice is to separate usernames with a period (.), an underscore (_) or a dash (-). These separators can be added to patterns. Example:
```properties
[firstname].[lastname]
```
would generate the username `john.smith`
#### Adding a string to a pattern
Same as the separators, it is possible to add a static string to username suggestions. For example, the pattern:
```properties
[lastname]_nyc
```
would generate the username `smith_nyc`
#### What if the API runs out of patterns?
The following pattern is used as the last resort:
`[C9_firstname][C9_lastname][#]`
#### `patterns` example
Given the following user data map, customFields and patterns:
```json
{
"firstname": "Carlos",
"lastname": "Álvarez",
"region": "CA",
"group": "5A"
}
```
```properties
patterns = [firstname].[lastname], [C1_firstname].[lastname], [firstname][lastname]_[region], [firstname][lastname]_[group], [lastname]_nyc, [firstname][lastname][#]
```
Then consecutive calls to the suggest method will return (in that order):
Generated username | Used pattern
------------ | -------------
carlos.alvarez | `[firstname].[lastname]`
c.alvarez | `[C1_firstname].[lastname]`
carlosalvarez_mx | `[firstname][lastname]_[region]`
carlosalvarez_5A | `[firstname][lastname]_[group]`
alvarez_nyc | `[lastname]_nyc`
carlosalvarez1 | `[firstname][lastname][#]`
carlosalvarez2 | `[firstname][lastname][#]`
carlosalvarez3 | `[firstname][lastname][#]`
... | `[firstname][lastname][#]`
> **Notes:**
- Special characters (e.g accents) are removed
- All text gets converted to lowercase
- Using a [#] will ignore the following patterns (as it will continue increasing the counter)
## Google API properties
Follow the steps in the [Google Apps domain configuration guide][configGuide] to configure these properties.
##### `apis.GoogleAPIs.domain`
**Description**: The Google Apps domain
**Example:** `apis.GoogleAPIs.domain=yourdomain.com`
-------------
##### `apis.GoogleAPIs.authUser`
**Description**: Admin user who created the project in the Google Developer Console.
**Example:** `apis.GoogleAPIs.authUser=admin@yourdomain.com`
-------------
##### `apis.GoogleAPIs.keyPath`
**Description**: Path to the file that stores the Google private key. Can be generated following the steps in: https://cloud.google.com/storage/docs/authentication#service_accounts
**Example:** `apis.GoogleAPIs.keyPath=./service_account_key.p12`
-------------
##### `apis.GoogleAPIs.serviceAccountEmail`
**Description**: Internal user for server side applications. Can be generated following the steps in:
https://cloud.google.com/storage/docs/authentication#service_accounts
**Note:** You should enable API scopes in the Google Admin Console. This scopes can be registered following the steps in: https://support.google.com/a/answer/162106?hl=en
The following scope to the service account should be added: https://www.googleapis.com/auth/admin.directory.user
**Example:** `apis.GoogleAPIs.serviceAccountEmail=1234567890123-abcdefghijklmnopqrstuvwxz01234567@developer.gserviceaccount.com`
-------------
##### `apis.GoogleAPIs.appName`
**Description**: This value is the project name in the Google Developer Console. Can be obtained following the steps in: https://developers.google.com/console/help/new/#creatingdeletingprojects
**Example:** `apis.GoogleAPIs.appName=My project`
## Cache location properties
##### `db.h2.name`
**Description**: The name of the H2 database `.mv.db` file.
**Default**: `usernames`
-------------
##### `db.h2.path`
**Description**: The path where the H2 database (`.mv.db` file) will be created.
**Default**: `./`
## SSL properties
##### `security.ssl.useSSL`
**Description**: Enables HTTPS support over SSL.
**Possible values**: `YES` and `NO`
**Default**: `NO`
-------------
##### `security.ssl.keyStorePath`
**Description**: The path where the KeyStore (`jks` file) is located. This can be generated executing:
```bash
keytool -genkey -alias sitename -keyalg RSA -keystore keystore.jks -keysize 2048
```
-------------
##### `security.ssl.keyStorePassword`
**Description**: The KeyStore password. Password provided when the `jks` file was generated.
-------------
##### `security.ssl.keyManagerPassword`
**Description**: Commonly the same as `keyStorePassword`. Can be different if it is not a self-generated certificate.
# Building
### Requirements
- [Java 7+](https://www.java.com/en/download/help/download_options.xml)
- [Maven](https://maven.apache.org/download.cgi)
## Build steps
To generate a `appsProvisioning-0.0.1.jar` file under *./target*:
**From bash**
From the project's root folder, run:
```bash
mvn clean install -Dmaven.test.skip=true
```
**From Eclipse**
1. Right click on the project
2. Run As > Maven build...
- Under **Goals:** use `clean install`
- Check the **Skip test** checkbox
> **Note:** Tests create and remove Google Apps accounts in a test domain. Therefore, they are discouraged unless you plan to submit a change that affects those tests.
# Feedback
If you have any questions or feedback, please let us know in the [forum][forum]!
## Impressions
This API logs the number of calls to `suggest`, `create` and `select` per Google Apps domain. No other information is ever collected. This helps us justify adding more resources and support to this API.
# License
Account provisioning for Google Apps is licensed under Apache 2.0. Full license text is available in the [LICENSE][license] file.
This is not an official Google product (experimental or otherwise), it is just code that happens to be owned by Google.
# Contributing
See [CONTRIBUTING][contributing].
A good starting point to look at the Java code is
```java
apps.provisioning.server.rest.ProvisioningAction
```
you can navigate through each of the methods: `suggest`, `select` and `create`.
[contributing]: https://github.com/google/...
[forum]: https://groups.google.com/forum/?hl=en#!forum/account-provisioning-for-google-apps
[api]: #api--examples
[building]: #building
[installing]: #installing
[config]: #configuration-properties
[customfields]: #customfields
[csv]: http://googledrive.com/host/0B0hbybT0K1l7fjR6aEt3bl9XZFVFS1RGV3RMNW16LXlIWmhuRXhUWGsxWndIV2p6cEtlSkE/csv.png
[useCasesDiagram]: http://googledrive.com/host/0B0hbybT0K1l7fjVIYnZDdG5jMjg3QUwyOFN3UVFTSFg4cnZHQ3VIU3JWc25hU0RWVlo2TXM/diagram3.png
[tokens]: http://googledrive.com/host/0B0hbybT0K1l7fjVIYnZDdG5jMjg3QUwyOFN3UVFTSFg4cnZHQ3VIU3JWc25hU0RWVlo2TXM/tokens.png
[bulkGif]: http://googledrive.com/host/0B0hbybT0K1l7fjR6aEt3bl9XZFVFS1RGV3RMNW16LXlIWmhuRXhUWGsxWndIV2p6cEtlSkE/bulk-demo.gif
[selfGif]: http://googledrive.com/host/0B0hbybT0K1l7fjR6aEt3bl9XZFVFS1RGV3RMNW16LXlIWmhuRXhUWGsxWndIV2p6cEtlSkE/self-demo.gif
[demoInfo]: http://googledrive.com/host/0B0hbybT0K1l7fjR6aEt3bl9XZFVFS1RGV3RMNW16LXlIWmhuRXhUWGsxWndIV2p6cEtlSkE/demoInfo.png
[configGuide]: https://github.com/google/account-provisioning-for-google-apps/blob/master/CONFIG.md
[contributing]: https://github.com/google/account-provisioning-for-google-apps/blob/master/CONTRIBUTING.txt
[license]: https://github.com/google/account-provisioning-for-google-apps/blob/master/LICENSE.txt