%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2015 Kivra %%% %%% Permission to use, copy, modify, and/or distribute this software for any %%% purpose with or without fee is hereby granted, provided that the above %%% copyright notice and this permission notice appear in all copies. %%% %%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %%% %%% @doc Erlang OAuth 2.0 implementation %%% @end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%_* Module declaration =============================================== -module(oauth2_priv_set). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([new/1]). -export([union/2]). -export([is_subset/2]). -export([is_member/2]). -export_type([priv_set/0]). %%%_ * Types ----------------------------------------------------------- %% Invariant: Children are sorted increasingly by name. -type priv_tree() :: {node, Name :: binary(), Children :: [priv_tree()]} | '*'. %% Invariant: %% The list of trees is sorted increasingly by the name of the root node. -type priv_set() :: [priv_tree()]. %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Constructs a new priv_set from a single path or a list of paths. %% A path denotes a single privilege. -spec new(binary() | [binary()]) -> priv_set(). new(Paths) when is_list(Paths) -> lists:foldl(fun union/2, [], [make_forest(Path) || Path <- Paths]); new(Path) when is_binary(Path) -> make_forest(Path). %% @doc Returns the union of Set1 and Set2, i.e., a set such that %% any path present in either Set1 or Set2 is also present in the result. -spec union(priv_set(), priv_set()) -> priv_set(). union([H1={node, Name1, _}|T1], [H2={node, Name2, _}|T2]) when Name1 < Name2 -> [H1|union(T1, [H2|T2])]; union([H1={node, Name1, _}|T1], [H2={node, Name2, _}|T2]) when Name1 > Name2 -> [H2|union([H1|T1], T2)]; union([{node, Name, S1}|T1], [{node, Name, S2}|T2]) -> [{node, Name, union(S1, S2)}|union(T1, T2)]; union(['*'|_], _) -> ['*']; %% '*' in union with anything is still '*'. union(_, ['*'|_]) -> ['*']; union([], Set) -> Set; union(Set, []) -> Set. %% @doc Return true if Set1 is a subset of Set2, i.e., if %% every privilege held by Set1 is also held by Set2. -spec is_subset(priv_set(), priv_set()) -> boolean(). is_subset([{node, N1, _}|_], [{node, N2, _}|_]) when N1 < N2 -> false; %% This tree isn't present in Set2 as per the invariant. is_subset(Set1 = [{node, N1, _}|_], [{node, N2, _}|T2]) when N1 > N2 -> is_subset(Set1, T2); is_subset([{node, Name, S1}|T1], [{node, Name, S2}|T2]) -> case is_subset(S1, S2) of true -> is_subset(T1, T2); false -> false end; is_subset(['*'|_], ['*'|_]) -> true; %% '*' is only a subset of '*'. is_subset(_, ['*'|_]) -> true; %% Everything is a subset of '*'. is_subset([], _) -> true; %% The empty set is a subset of every set. is_subset(_, _) -> false. %% @doc Returns true if Path is present in Set, i.e, if %% the privilege denoted by Path is contained within Set. -spec is_member(binary(), priv_set()) -> boolean(). is_member(Path, Set) -> is_subset(make_forest(Path), Set). %%%_* Private functions ================================================ -spec make_forest(binary() | list()) -> priv_set(). make_forest(Path) when is_binary(Path) -> make_forest(binary:split(Path, <<".">>, [global])); make_forest(Path) when is_list(Path) -> [make_tree(Path)]. -spec make_tree([binary()]) -> priv_tree(). make_tree([<<"*">>|_]) -> '*'; make_tree([N]) -> make_node(N, []); make_tree([H|T]) -> make_node(H, [make_tree(T)]). -spec make_node(binary(), [priv_tree()]) -> priv_tree(). make_node(Name, Children) -> {node, Name, Children}. %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: