Permission Reference Guide
About
The info-beamer permission systems allows you to grant fine-grained access permissions to your account for other users. In addition you can create any number of additional access permissions for your own account. This can be used to create limited access API keys to your account and allows controlled OAuth2 based access to our account.
Use cases
The permission system was designed to allow you to model a wide variety of use cases. Here are some examples:
- Allow others to work in your account. With either full permissions for fine grained permissions. As an example you can invite an account to act as a device administrator with permission to only manage your devices, nothing else.
- Limit API keys so they can only be used for a single purpose. You can for example create an API key for your monitoring system that can only query the current account information, nothing more.
- If you build your own management dashboard: Have all customers in a single info-beamer account and use access control lists to limit what each of your customer can do in that account.
- Further restrict access by time or source IP address, to prevent abuse in case of stolen credentials.
Of course the complete permission system is an API as well. So you can automate every aspect if you want.
General concept
In the earlier days of info-beamer hosted, in general you only had a single account with one password and a single API key. Sharing accounts would most likely involve sharing your password. If you started automating your signage installation using the API, you only had one API key with full access to your account. Often that access was way too broad and a stolen or leaked API key could grant complete access. Clearly not optimal.
The permission system fixes these issues and creates a highly flexible system of granting access to your account to other users and to create limited API keys that only allow the access required for a certain task.
Using the new permission page you can create and manage access to your account. An access is always governed by an access control list (ACL) which in turn is a collection of policies. You can reuse policies in multiple ACLs and assign those to one or many users accessing your account.
When you create a new access to your own account or are granted access to the account of another user, you can easily switch between your and their accounts using the account shared access page. If you work mostly in the account of another user, you can set their account as a default as well. When you log in, you'll automatically work in their account. Of course you can always switch back to your own account.
Switching between accounts only changes the dashboard, devices, setup, package, asset permission and playlist tabs. Your account settings, info-beamer shop or community access are independant and you cannot perform any related tasks for another users. This basically means that everything reachable by clicking the top-right menu will always only affect your own account.
Access
An access describes a relationship between an inviting account and the invited account. It is possible to create a self-access where the invited account is the inviting account.
An access has a lifecycle that starts when it is created by the inviting account. Have a look at the following flow chart:
When an access for another account is created, its state is Unjoined access. The other user cannot use the access yet and the access will not be visible on the account shared access page for that user.
The access can then be joined by the invited account using their permission page or using the join share API call. Once the invited user joined the access it is now in the Active access state. The user has access to your account according to the attached access control list.
An access can have an expiry date at which it automatically expires without action from either the invited or inviting user. This can be used to create time limited access to your account and frees you from manually revoking access.
If you want to prevent the invited user from any further access to your account, you can always delete an access. Similarly the invited user can leave an access if they no longer need it.
A self-access is an access to your own account. There is always a system-managed self-access with full permissions. You cannot delete this access as you otherwise would lose access to your own account. In addition to this, you can create additional self-accesses. It's not required to explicitly join those as the end up in the Active access state automatically.
On the permission page you can see the list of accesses for your account. Each access has an associated API key that can be used to control your (in case of a self-access) or another user's account using the API.
ACL
Access control lists (ACLs) describe which actions can be executed by an access in your account. An access can only have a single ACL attached to it. Access control list consist of any number of policies.
You might have per-user ACLs if some users require special permissions or generic ACLs that you assign to multiple users accessing your account. You can always change the ACL of an access to your account if required.
ACL evaluation
All API calls issued either directly or indirectly by using the info-beamer dashboard are always checked against the currently active access and its current ACL. Any change make to ACLs/policies are immediately effective. An ACL evaluation always either allows or denies the requested action. If the action is denied, the requested API call will return an 403 error.
It's important to understand how ACLs get evaluated so you can create secure policies. All policy attached to the tested ACL are individually tested. Each policy can return one of the following three results (See below for details):
Policy result | When this is returned |
---|---|
Allow | If the final result of evaluating all statements in a policy returns Allow |
Deny | If the final result of evaluating all statements in a policy returns Deny |
Undecided | If the final result of evaluating all statements in a policy returns Undecided. This happens if none of the actions or conditions within a policy match the current request. This also happens if there are no statements at all in a policy. |
Multiple policy results are combined according to the following flow chart:
Properties to note:
- If there is no policy attached to an ACL, the request will be denied by default.
- If any of the policies return Deny, the request will be denied and no other policy can revert that decision.
- If none of the polices match and thus evaluate to Undecided the request will be denied.
- A request can only be allowed if at least one policy returns Allow for the request.
- Multiple policies with an Allow result create a union of allowed actions.
- The order in which policies are evaluated is irrelevant.
Policies
Policies consist of multiple statements that each decide if an action in your account is allowed or denied. Each statement can be conditional based on environmental, requester or properties of the accesses object (e.g. device).
As an example you might have a policy that only allows access if the IP range of the requesting API client is within a certain network block. Or one that limits access to an account only from Monday to Friday. Similarly a policy might prevent a user from renaming assets outside a single directory. See below for examples.
Policies are written in JSON. info-beamer provides a couple of "managed" policies for common use cases. For more specialized requirements you can create your own policies.
Policy example
Here is what a policy might look like:
{ "Version": 1, "Statements": [ { "Action": "*", "Condition": { "NotIPMatch": { "request:ip": "62.0.0.0/8" } }, "Effect": "deny" } ] }
Every policy document must have a Version property set to 1. Every policy must also have a Statement list. While it's possible to have an empty Statement list, it's not particularly useful as such a policy will have no effect and will return Undecided when evaluated.
Each Statement requires at least Action and Effect. Optionally a statement can have one or more Condition evaluators.
Action can either be a string value or a list describing which actions this statement should match. A list of actions might look like this ["device:*", "setup:delete"]. Note the use of the * in the example. It's a wildcard to match any action for devices.
If you just want to match a single action, use a string value instead like this "asset:delete". All API calls that result in an account action have their action documented on the API page.
A statement must have an effect. The value of Effect must be either "allow" or "deny".
The above example basically says: "Deny all actions to the account unless the IP of the accessing client is from the 62.0.0.8/8 range".
It's possible to have multiple conditions like this:
"Condition": { "NumericEquals": { "setup:id": [1,2,3] }, "StringLike": { "request:user-agent": ["*curl*", "*wget*"], "auth:access:object:email": "*@example.net" } }
NumericEquals is an evaluator. You can have multiple evaluators in a single condition. For the condition to match, all evaluators must match. Similar an evaluator can test multiple request values. In the example the StringLike evaluator tests both "request:user-agent" and "auth:access:object:email. Like with multiple evaluators, multiple tests for a single evaluator only match if all of them match.
Finally a single test can have multiple condition values. For example the requested setup:id must be one of 1, 2 or 3. Summarized in an image the condition evaluation works like this.
Additionally every Evaluator can be negated by prepending Not. So while
"NumericEquals": { "setup:id": [1,2,3] }
matches if setup:id is one of the given values, the following
"NotNumericEquals": { "setup:id": [1,2,3] }
only matches if setup:id is "not one" of the values.
Policy evaluation
Each policy is evaluated according to the following flow chart:
Properties to note:
- An empty policy returns Undecided.
- If none of the statements match, so either no Action matches or none of the Conditions match, the policy returns Undecided.
- The last seen Allow or Deny decide the final result.
- If your policy only either returns Allow or Undecided, the policy can be easily combined with other such policies to create a union of all allowed actions.
- If your policy only either returns Deny or Undecided, it can be combined with other policies to restrict what actions are allowed.
Request context
In the above example you've seen values like setup:id and request:user-agent. These values provide you a context when evaluating the condition. Some values depend on who makes the request. Other values like request:time are based on the current time when the request was made.
Many API calls only change or delete a single object. Such requests, like the Update Device API call, provide additional request context values. You get at least the id (in device:id) as well as other useful properties like the device:description or device:location. So you can, for example, limit requests based on a device's location. The API documentation provide information about such additional request context values.
For calls that modify an object you often get additional information about the changed values. For the Update Device call you get the new location value in update:location and can make a decision based on it.
Always available context values
The following values are always available and can be used in your condition evaluators:
Context value name | Type | Description |
---|---|---|
auth:access:id | Number | The ID of the access in use by the current call. |
auth:access:description | String | The description of that access. |
auth:access:acl:id | Number | The ACL id. |
auth:access:acl:name | String | The name of that ACL. |
auth:access:subject:email | String | The email of the account issuing the request. |
auth:access:subject:2fa | Boolean | If the requesting subject has been authenticated using two factor authentication. |
auth:access:object:email | String | The email of the account being accessed. |
auth:access:by-owner | Boolean | Is this a self access? So auth:access:subject:email == auth:access:object:email |
auth:access:rate | Number | The number of requests for this access averaged over the last 10 seconds. |
auth:is-session | Boolean | There are two ways of authentication in info-beamer hosted. Session based and ad-hoc. When you log into info-beamer hosted using the dashboard or the session API call you create a new session. You can issue multiple requests towards the API. Once you log out, the session is gone and can no longer be used. Ad-hoc request on the other hand are authenticated using the API keys. An API key is permanently tied to an access. |
request:time | Number | The current unix timestamp. |
request:ip | String | The remote address of the client issuing the request. |
request:user-agent | Optional String | The User-Agent header of the client. If none is provided, this value is absent. |
request:method | String | The lower case HTTP verb for the request. So get, post or delete. |
request:origin:host | Optional String | If the requests is issued by a browser from another domain, the origin header is parsed and the hostname part can be used in conditions. |
Most requests provide additional values depending on the API call. The API documentation lists those additional context values for each call.
Userdata context
Some objects (devices, setups, packages, assets) can have userdata associated with them. Most of the calls for those objects also allow you to use some of the userdata for making policy decisions.
A userdata object can have any shape, but you can only test against numbers, strings and boolean top-level values. Consider the userdata of an object set to:
{ "some-number": 1234, "a-string": "Hello world", "can access": true, "list": [1,2,3,4,5], "obj": {"foo": "bar"} }
The following request context values will be available for evaluation in your policy:
Context value name | Type | Value |
---|---|---|
userdata:some-number | Numeric | 1234 |
userdata:a-string | String | "Hello World" |
userdata:can access | Boolean | true |
"list" and "obj" will not be available as they are complex objects.
Policy Evaluators
info-beamer hosted offers various policy evaluators that allow you to test request context values. Each can be prefixed by Not to invert the check. As an example NumericLess could also be expressed as NotNumericGreaterEquals.
Some evaluators have optional arguments that can further specify their behaviour. An example is WeekDayEquals(Europe/Berlin) in which the value given in the braces specify the timezone used.
Evaluators are typed. If you use an evaluator with an request context values with a non-matching type, the evaluator will not match.
Evaluator | Expected type | Description |
---|---|---|
StringEquals | String | Tests if two strings are equal. |
StringEqualsIgnoreCase | String | Tests if two strings are equal while ignoring case differences. |
StringLike | String | Matches based on a pattern. See below. |
StringLikeIgnoreCase | String | Matches based on a pattern while ignoring case differences. See below. |
NumericEquals | Number | True if the request and condition values are equal. |
NumericLess | Number | True if the request value is smaller than the condition value. |
NumericLessEquals | Number | True if the request value is smaller or equal to the condition value. |
NumericGreater | Number | True if the request value is greater than the condition value. |
NumericGreaterEquals | Number | True if the request value is greater or equal to the condition value. |
Boolean | Boolean | True if the request value matches the condition value. |
Exists | Any | True if the request values exists. |
IPMatch | String | True if the request value is in the network given. The condition value is a string in the form a.b.c.d/netmask or a.b.c.d. |
WeekDayEquals | Number | True if the unix timestamp in the request value is the given day of the week. The day must be given as a number between 1 (Monday) and 7 (Sunday). Defaults to using the UTC timezone. |
WeekDayEquals(timezone) | Number | True if the request value is the given day of the week. The timezone given is used for calculating the day. See below for an example. |
DateAfter | Number | True if the unix timestamp request value is after or equal to the provided timestamp. Uses UTC time. The condition value must be a string given in the form YYYY-MM-DD HH:MM:SS |
DateAfter(timezone) | Number | Same, except uses the provided timezone |
DateBefore | Number | True if the request value is before or equal to the provided timestamp. Uses UTC time. |
DateBefore(timezone) | Number | Same, except uses the provided timezone |
TimeAfter | Number | True if the unix timestamp request value is after or equal to the provided time of the request value. Uses UTC time. The condition value must be a string given in the form HH:MM. |
TimeAfter(timezone) | Number | Same, except uses the provided timezone |
TimeBefore | Number | True if the request value is before or equal to the provided time of the request value. Uses UTC time. |
TimeBefore(timezone) | Number | Same, except uses the provided timezone |
Evaluator Patterns
The StringLike and StringLikeIgnoreCase evaluator use pattern based matching. The following special characters can be used inside the pattern:
* | matches everything |
? | matches any single character |
[seq] | matches any character in seq |
[!seq] | matches any character not in seq |
See below for example usage of those patterns.
Policy Examples
Here are some of the restrictions you can express using policies.
Time limited access
The following policy denies requests not issued on a workday (Monday to Friday) based on the request time converted into the Europe/Berlin timezone.
{ "Version": 1, "Statements": [ { "Action": "*", "Condition": { "NotWeekDayEquals(Europe/Berlin)": { "request:time": [1, 2, 3, 4, 5] } }, "Effect": "deny" } ] }
Note that this is a restricting policy that only denies actions, but doesn't allow any. So you need to combined this policy with other policies that allows access to your account. On its own, this policy will always deny any action as there is no statement allowing anything.
Limit asset modification to directory
This policy can be used to prevent access to any asset outside a single given directory. It matches if the asset:filename request value doesn't start with test/.
{ "Version": 1, "Statements": [ { "Action": [ "asset:upload", "asset:import", "asset:update:*", "asset:delete" ], "Condition": { "NotStringLike": { "asset:filename": "test/*" } }, "Effect": "deny" } ] }
Note that this is a restricting policy that only denies actions. See the comment in the previous policy.
Prevent day time reboots
Here is a policy using TimeAfter and TimeBefore to prevent device reboots using the device reboot API call in the middle of the day.
{ "Version": 1, "Statements": [ { "Action": "device:reboot", "Condition": { "TimeAfter(Europe/Berlin)": { "request:time": "06:00" }, "TimeBefore(Europe/Berlin)": { "request:time": "22:00" } }, "Effect": "deny" } ] }
Note that this is a restricting policy that only denies actions. See the comment in the previous policy.
Prevent device configuration
The following policy prevents an access from modifying the device configuration. It matches the obvious action device:config:write which is used by the device config API call.
Additionally the user might manually edit the configuration using a remote shell. This can be prevented by denying device:execute and device:session.
Finally package creation should be denied using package:create as a package might in theory run a package service with root permission which could then overwrite the configuration on a device.
{ "Version": 1, "Statements": [ { "Action": [ "device:config:write", "device:execute", "device:session", "package:create" ], "Effect": "deny" } ] }
Note that this is a restricting policy that only denies actions. See the comment in the previous policy.
Use cases
Allow sync of one specific package
In earlier versions, info-beamer hosted supported "webhooks" which were a way to trigger package updates by issuing unauthenticated to a 'secret' url automatically generated for each of your packages.
This feature can now be simulated using permissions. Follow these steps to allow updates to a package with an example package_id of 1234. Once advantage of using the permission system to implement this feature is that you can further lock down that access, so in addition to the single policy shown below, you might limit the requests using other policies that, for example, only allow a specific source IP address.
Create a new policy
{ "Statements": [ { "Action": "*", "Effect": "deny" }, { "Action": "package:update:sync", "Condition": { "NumericEquals": { "package:id": 1234 } }, "Effect": "allow" } ], "Version": 1 }
This policy first denies every request, then only allows calling the package sync API call for the package with ID 1234. Any other action or access to packages with other ids are denied. Have a look at the the policy evaluation flow to see how the above statements are evaluated. Name the policy Allow sync of package 1234, so you remember what why you created it.
Create an ACL
Create a new ACL and add the new policy as its only policy. You might name this ACL Allow sync of package 1234.
Create a new access
Create a new self-access to your account. You might set its description to Allow sync of package 1234 so you remember why it exists. Use the ACL created above and create the access.
Then click on "Accessible accounts" to see all accesses available to you. You'll see your new access. Click on "Access action" and "View API Key". This API key is now limited to syncing the package 1234 only.
Issue sync process
Finally you can create a request url you can use to sync the package 1234. Here's how you would issue the request using curl:
curl https://info-beamer.com/api/v1/package/1234/sync?api-key=<API-KEY>
git push only access
If you're using git to push packages to your account you probably have your current API key in ~/.netrc. This key allows complete access to your account. Clearly that's more than needed. It's now possible to create an API key that is limited to only git push. Create a new policy like the one below:
{ "Statements": [ { "Action": "*", "Effect": "deny" }, { "Action": "package:update:push", "Effect": "allow" } ], "Version": 1 }
Then create an ACL, assign this policy and use it in a new self-access. The resulting API key is then limited to git push only.
Allow account access only from your corporate network
You can limit access to your account to your own IP ranges. This can be used to prevent users from accessing your account if they are outside those ranges. Create the following policy and adapt it to your network:
{ "Statements": [ { "Action": "*", "Condition": { "NotIPMatch": { "request:ip": [ "62.1.0.0/16", "127.0.0.1/8" ] } }, "Effect": "deny" } ], "Version": 1 }
Of course you'll have to replace the example IP addresses with your own addresses. You can either specify subnets (using the slash notation) or single IP addresses in the NotIPMatch evaluator. As multiple conditions are logically OR-ed, you can specify multiple IP addresses or ranges and the policy matches and denies access if the request is made from outside of any of them.
Add this policy to your ACLs and it will deny any request outside the specified IP addresses.