Skip to content

Instantly share code, notes, and snippets.

@ncthbrt
Last active January 12, 2018 08:58
Show Gist options
  • Save ncthbrt/0a20d5f3745d554511891a4f98ee9d25 to your computer and use it in GitHub Desktop.
Save ncthbrt/0a20d5f3745d554511891a4f98ee9d25 to your computer and use it in GitHub Desktop.
Authorization.re
type permission =
| Permission(string, string);
module PermissionComparision = {
type t = permission;
let compare = (Permission(name1, _), Permission(name2, _)) => String.compare(name1, name2);
};
module PermissionSet = Set.Make(PermissionComparision);
type role = {
name: string,
description: string,
permissions: PermissionSet.t
};
type organization = {
name: string,
organizationId: string
};
let isSameOrgAs = ({organizationId: id1}, {organizationId: id2}) => id1 == id2;
type user = {
email: string,
userId: string,
organizations: list((organization, list(role)))
};
let isSameUserAs = ({userId: id1}, {userId: id2}) => id1 == id2;
type entity =
| User(user)
| Organization(organization);
let isSameEntityAs = (entity1, entity2) =>
switch (entity1, entity2) {
| (User(user1), User(user2)) => user1 |> isSameUserAs(user2)
| (Organization(org1), Organization(org2)) => org1 |> isSameOrgAs(org2)
| _ => false
};
type accessToken = {
name: string,
permissions: PermissionSet.t,
owner: entity
};
type credential =
| UserCredential(user)
| AccessToken(accessToken);
type verdict =
| Authorized
| Unauthorized;
let (%>=) = (set1, set2) => PermissionSet.subset(set2, set1);
let (%+) = (set1, set2) => PermissionSet.union(set1, set2);
let agglomeratePermissions = (roles) =>
roles |> List.fold_left((set, {permissions}: role) => set %+ permissions, PermissionSet.empty);
let toSome = (x) => Some(x);
let tryFindOrganization = ({organizations}, targetOrg) =>
try (
organizations
|> List.find(((organization, _)) => organization |> isSameOrgAs(targetOrg))
|> toSome
) {
| _ => None
};
let isUserAuthorized = (user, org, requiredPermissions) =>
switch (tryFindOrganization(user, org)) {
| Some((_, roles)) when agglomeratePermissions(roles) %>= requiredPermissions => Authorized
| Some(_) => Unauthorized
| None => Unauthorized
};
let isAuthorized = (entity, requiredPermissions, credential) => {
let requiredPermissionSet = PermissionSet.of_list(requiredPermissions);
switch (credential, entity) {
/* User trying to gain access to org */
| (UserCredential(user), Organization(org)) => isUserAuthorized(user, org, requiredPermissionSet)
/* User trying to gain access to own user account */
| (UserCredential(authUser), User(user)) when authUser |> isSameUserAs(user) => Authorized
/* User trying to gain access to ANOTHER user account */
| (UserCredential(_), User(_)) => Unauthorized
/* Access token trying to gain access to correct entity */
| (AccessToken({owner, permissions}), entity)
when entity |> isSameEntityAs(owner) && permissions %>= requiredPermissionSet =>
Authorized
/* Access token trying to gain access to a DIFFERENT entity for which it was issued */
| (AccessToken(_), _) => Unauthorized
}
};
@ncthbrt
Copy link
Author

ncthbrt commented Jan 11, 2018

Thanks for the detailed review @yawaramin

L6: note that we usually use compare for value comparison, so changing its semantics may result in unexpected behaviour. Specifically, think about where the comparison function will be used. With the current implementation, you won't be able to add a permission to a set which contains a permission with the same name but different password field.

The second parameter is actually for a description field which has no logic significance, so I actually think it'd be best to remove that from this module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment