p1_oauth2-0.6.11/0000755000232200023220000000000014233730101013741 5ustar debalancedebalancep1_oauth2-0.6.11/README.md0000644000232200023220000001350314233730101015222 0ustar debalancedebalance# P1 OAuth2 [![CI](https://github.com/processone/p1_oauth2/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/processone/p1_oauth2/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/processone/p1_oauth2/badge.svg?branch=master&service=github)](https://coveralls.io/github/processone/p1_oauth2?branch=master) [![Hex version](https://img.shields.io/hexpm/v/p1_oauth2.svg "Hex version")](https://hex.pm/packages/p1_oauth2) This library is designed to simplify the implementation of the server side of OAuth2 (http://tools.ietf.org/html/rfc6749). It provides **no** support for developing clients. See [oauth2_client](https://github.com/kivra/oauth2_client) for support in accessing Oauth2 enabled services. oauth2 is released under the terms of the [MIT](http://en.wikipedia.org/wiki/MIT_License) license Current stable version: [0.6.0](https://github.com/kivra/oauth2/tree/0.6.0) Current α alpha version: [0.6.x](https://github.com/kivra/oauth2) copyright 2012-2014 Kivra ## tl;dr ### Examples Check out the [examples](https://github.com/kivra/oauth2_example). ### Related projects Webmachine server implementation by Oauth2 contributor Ivan Martinez: [oauth2_webmachine](https://github.com/IvanMartinez/oauth2_webmachine). Redis backed Oauth2 [backend](https://github.com/interline/oauth2_redis_backend). ## Concepts ### Tokens A token is a (randomly generated) string provided to the client by the server in response to some form of authorization request. There are several types of tokens: * *Access Token*: An access token identifies the origin of a request for a privileged resource. * *Refresh Token*: A refresh token can be used to replace an expired access token. #### Expiry Access tokens can (optionally) be set to expire after a certain amount of time. An expired token cannot be used to gain access to resources. ### Identities A token is associated with an *identity* -- a value that uniquely identifies a user, client or agent within your system. Typically, this is a user identifier. ### Scope The scope is handled by the backend implementation. The specification outlines that the scope is a space delimetered set of parameters. This library has been developed with the following in mind. Scope is implemented as a set and loosely modeled after the Solaris RBAC priviliges, i.e. `solaris.x.*` and implemented as a [MAC](http://en.wikipedia.org/wiki/Mandatory_access_control) with the ability to narrow the scope but not extend it beyond the predefined scope. But since the scope is opaque to this Oauth2 implementation you can use the scoping strategy that best suit your workflow. There is a utility module to work with scope. The recommendation is to pass a Scope as a list of binaries, i.e. `[<<"root.a.c.b">>, <<"root.x.y.z">>]` you can then validate these against another set like: ``` erlang > oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]), oauth2_priv_set:new([<<"root.*">>])). true > oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]), oauth2_priv_set:new([<<"root.x.y">>])). false > oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]), oauth2_priv_set:new([<<"root.a.*">>, <<"root.x.y">>])). true ``` ### Clients If you have many diverse clients connecting to your service -- for instance, a web client and an iPhone app -- it's desirable to be able to distinguish them from one another and to be able to grant or revoke privileges based on the type the client issuing a request. As described in the OAuth2 specification, clients come in two flavors: * *Confidential* clients, which can be expected to keep their credentials from being disclosed. For instance, a web site owned and operated by you could be regarded as confidential. * *Public* clients, whose credentials are assumed to be compromised the moment the client software is released to the public. Clients are distinguished by their identifiers, and can (optionally) be authenticated using a secret key shared between the client and server. ## Testing If you want to run the EUnit test cases, you can do so with: $ make ct ## Customization The library makes no assumptions as to how you want to implement authentication and persistence of users, clients and tokens. Instead, it provides a behavior (`oauth2_backend`) with functions that needs to be implemented. To direct calls to a different backend module, simply set `{backend, your_backend_module}` in the `oauth2` section of your app.config. Look at [oauth2_mock_backend](test/oauth2_mock_backend.erl) for how a backend can be implemented. The following example demonstrates a basic app.config section for oauth2. ``` erlang [ {oauth2, [ %% Default expiry_time for access_tokens unless %% overridden per flow {expiry_time, 3600} ,{backend, backend_goes_here} %% Optional expiry_time override per flow ,{password_credentials, [ {expiry_time, 7200} ]} ,{client_credentials, [ {expiry_time, 86400} ]} ,{refresh_token, [ {expiry_time, 2592000} %% 30 Days ]} ,{code_grant, [ %% Recommended absolute expiry time from the spec {expiry_time, 600} ]} ]} ]. ``` A complete list of functions that your backend must provide is available by looking at `oauth2_backend.erl`, which contains documentation and function specifications. To implement a custom token generation backend you can change your app.config as such: ``` erlang [ {oauth2, [ {token_generation, YOUR_TOKEN_GENERATOR} ]} ]. ``` The default token generator is called oauth2_token. To implement your own you should create your own module implementing the oauth2_token_generation behavior exporting one function generate/0. p1_oauth2-0.6.11/rebar.config.script0000644000232200023220000001074514233730101017535 0ustar debalancedebalance%%%---------------------------------------------------------------------- %%% File : rebar.config.script %%% Author : Mickael Remond %%% Purpose : Rebar build script. Compliant with rebar and rebar3. %%% Created : 24 Nov 2015 by Mickael Remond %%% %%% Copyright (C) 2002-2021 ProcessOne, SARL. All Rights Reserved. %%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%% %%%---------------------------------------------------------------------- SysVersion = lists:map(fun erlang:list_to_integer/1, string:tokens(erlang:system_info(version), ".")), IsRebar3 = case application:get_key(rebar, vsn) of {ok, VSN} -> [VSN1 | _] = string:tokens(VSN, "-"), [Maj|_] = string:tokens(VSN1, "."), (list_to_integer(Maj) >= 3); undefined -> lists:keymember(mix, 1, application:loaded_applications()) end, ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) -> {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of {value, {_, V1}, V2} -> {V1, V2}; false -> {if Tail == [] -> Default; true -> [] end, Cfg} end, case Tail of [] -> [{Key, Op(OldVal)} | PartCfg]; _ -> [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg] end end, ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end, ModCfgS = fun(Cfg, Keys, Val) -> ModCfg0(ModCfg0, Cfg, Keys, fun(_V) -> Val end, "") end, FilterConfig = fun(F, Cfg, [{Path, true, ModFun, Default} | Tail]) -> F(F, ModCfg0(ModCfg0, Cfg, Path, ModFun, Default), Tail); (F, Cfg, [_ | Tail]) -> F(F, Cfg, Tail); (F, Cfg, []) -> Cfg end, AppendStr = fun(Append) -> fun("") -> Append; (Val) -> Val ++ " " ++ Append end end, AppendList = fun(Append) -> fun(Val) -> Val ++ Append end end, Rebar3DepsFilter = fun(DepsList) -> lists:map(fun({DepName,_, {git,_, {tag,Version}}}) -> {DepName, Version}; (Dep) -> Dep end, DepsList) end, GlobalDepsFilter = fun(Deps) -> DepNames = lists:map(fun({DepName, _, _}) -> DepName; ({DepName, _}) -> DepName end, Deps), lists:filtermap(fun(Dep) -> case code:lib_dir(Dep) of {error, _} -> {true,"Unable to locate dep '"++atom_to_list(Dep)++"' in system deps."}; _ -> false end end, DepNames) end, GithubConfig = case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of {"true", Token} when is_list(Token) -> CONFIG1 = [{coveralls_repo_token, Token}, {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")}, {coveralls_commit_sha, os:getenv("GITHUB_SHA")}, {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")}], case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" andalso string:tokens(os:getenv("GITHUB_REF"), "/") of [_, "pull", PRNO, _] -> [{coveralls_service_pull_request, PRNO} | CONFIG1]; _ -> CONFIG1 end; _ -> [] end, Rules = [ {[deps], IsRebar3, Rebar3DepsFilter, []}, {[plugins], IsRebar3, AppendList([pc]), []}, {[deps], os:getenv("TEST") == "true", AppendList([{meck, ".*", {git, "https://github.com/eproxus/meck", {tag, "0.9.2"}}}, {proper, ".*", {git, "https://github.com/proper-testing/proper", {tag, "v1.3"}}} ]), []}, {[plugins], os:getenv("COVERALLS") == "true", AppendList([{coveralls, {git, "https://github.com/processone/coveralls-erl.git", {branch, "addjsonfile"}}} ]), []}, {[deps], os:getenv("USE_GLOBAL_DEPS") /= false, GlobalDepsFilter, []} ], Config = FilterConfig(FilterConfig, CONFIG, Rules) ++ GithubConfig, %io:format("Rules:~n~p~n~nCONFIG:~n~p~n~nConfig:~n~p~n", [Rules, CONFIG, Config]), Config. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.11/CHANGELOG.md0000644000232200023220000000144614233730101015557 0ustar debalancedebalance# Version 0.6.11 * Generate documentation when generating hex.pm package * Remove usage of deprecated crypto functions * Improve errors reporting # Version 0.6.10 * Switch from using Travis to Github Actions as CI # Version 0.6.9 * Dialyzer: Update Response record definition: fields may be undefined # Version 0.6.8 * Update travis config # Version 0.6.6 * Update copyright year # Version 0.6.3 * Remove last remainig crypto:rand\_bytes() usage # Version 0.6.2 * Make tests works with erlang R20 * Fix Travis-CI compilation # Version 0.6.1 * Repository is now forked to p1_oauth2 for consistency (Mickaël Rémond) * Initial release on Hex.pm (Mickaël Rémond) * Standard ProcessOne build chain (Mickaël Rémond) * Setup Travis-CI and test coverage, tests still needed (Mickaël Rémond) p1_oauth2-0.6.11/Makefile0000644000232200023220000000053114233730101015400 0ustar debalancedebalanceREBAR ?= ./rebar .PHONY: all deps compile clean test ct all: deps compile deps: $(REBAR) get-deps compile: $(REBAR) compile clean: $(REBAR) clean rm -f test/*.beam rm -f erl_crash.dump rm -rf deps/ rm -rf ebin/ rm -rf _build/ test: TEST=true $(REBAR) get-deps TEST=true $(REBAR) compile TEST=true $(REBAR) eunit skip_deps=true p1_oauth2-0.6.11/src/0000755000232200023220000000000014233730101014530 5ustar debalancedebalancep1_oauth2-0.6.11/src/oauth2_response.erl0000644000232200023220000002204514233730101020357 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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_response). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([new/1]). -export([new/2]). -export([new/4]). -export([new/6]). -export([new/7]). -export([access_token/1]). -export([access_token/2]). -export([access_code/1]). -export([access_code/2]). -export([refresh_token/1]). -export([refresh_token/2]). -export([refresh_token_expires_in/1]). -export([refresh_token_expires_in/2]). -export([resource_owner/1]). -export([resource_owner/2]). -export([expires_in/1]). -export([expires_in/2]). -export([scope/1]). -export([scope/2]). -export([token_type/1]). -export([to_proplist/1]). -ifndef(pre17). -export([to_map/1]). -endif. -export_type([response/0]). %%%_* Macros =========================================================== -define(TOKEN_TYPE, <<"bearer">>). %%%_ * Types ----------------------------------------------------------- -record(response, { access_token :: oauth2:token() | undefined ,access_code :: oauth2:token() | undefined ,expires_in :: oauth2:lifetime() | undefined ,resource_owner :: term() ,scope :: oauth2:scope() | undefined ,refresh_token :: oauth2:token() | undefined ,refresh_token_expires_in :: oauth2:lifetime() | undefined ,token_type = ?TOKEN_TYPE :: binary() }). -type response() :: #response{}. -type token() :: oauth2:token(). -type lifetime() :: oauth2:lifetime(). -type scope() :: oauth2:scope(). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- -spec new(token()) -> response(). new(AccessToken) -> #response{access_token = AccessToken}. -spec new(token(), lifetime()) -> response(). new(AccessToken, ExpiresIn) -> #response{access_token = AccessToken, expires_in = ExpiresIn}. -spec new(token(), lifetime(), term(), scope()) -> response(). new(AccessToken, ExpiresIn, ResOwner, Scope) -> #response{ access_token = AccessToken , expires_in = ExpiresIn , resource_owner = ResOwner , scope = Scope }. -spec new(token(), lifetime(), term(), scope(), token(), lifetime()) -> response(). new(AccessToken, ExpiresIn, ResOwner, Scope, RefreshToken, RExpiresIn) -> #response{ access_token = AccessToken , expires_in = ExpiresIn , resource_owner = ResOwner , scope = Scope , refresh_token = RefreshToken , refresh_token_expires_in = RExpiresIn }. -spec new(_, lifetime(), term(), scope(), _, _, token()) -> response(). new(_, ExpiresIn, ResOwner, Scope, _, _, AccessCode) -> #response{ access_code = AccessCode , expires_in = ExpiresIn , resource_owner = ResOwner , scope = Scope }. -spec access_token(response()) -> {ok, token()} | {error, not_set}. access_token(#response{access_token = undefined}) -> {error, not_set}; access_token(#response{access_token = AccessToken}) -> {ok, AccessToken}. -spec access_token(response(), token()) -> response(). access_token(Response, NewAccessToken) -> Response#response{access_token = NewAccessToken}. -spec access_code(response()) -> {ok, token()} | {error, not_set}. access_code(#response{access_code = undefined}) -> {error, not_set}; access_code(#response{access_code = AccessCode}) -> {ok, AccessCode}. -spec access_code(response(), token()) -> response(). access_code(Response, NewAccessCode) -> Response#response{access_code = NewAccessCode}. -spec expires_in(response()) -> {ok, lifetime()} | {error, not_set}. expires_in(#response{expires_in = undefined}) -> {error, not_set}; expires_in(#response{expires_in = ExpiresIn}) -> {ok, ExpiresIn}. -spec expires_in(response(), lifetime()) -> response(). expires_in(Response, NewExpiresIn) -> Response#response{expires_in = NewExpiresIn}. -spec scope(response()) -> {ok, scope()} | {error, not_set}. scope(#response{scope = undefined}) -> {error, not_set}; scope(#response{scope = Scope}) -> {ok, Scope}. -spec scope(response(), scope()) -> response(). scope(Response, NewScope) -> Response#response{scope = NewScope}. -spec refresh_token(response()) -> {ok, token()} | {error, not_set}. refresh_token(#response{refresh_token = undefined}) -> {error, not_set}; refresh_token(#response{refresh_token = RefreshToken}) -> {ok, RefreshToken}. -spec refresh_token(response(), token()) -> response(). refresh_token(Response, NewRefreshToken) -> Response#response{refresh_token = NewRefreshToken}. -spec refresh_token_expires_in(response()) -> {ok, lifetime()} | {error, not_set}. refresh_token_expires_in(#response{refresh_token = undefined}) -> {error, not_set}; refresh_token_expires_in(#response{refresh_token_expires_in = RefreshTokenExpiresIn}) -> {ok, RefreshTokenExpiresIn}. -spec refresh_token_expires_in(response(), lifetime()) -> response(). refresh_token_expires_in(Response, NewRefreshTokenExpiresIn) -> Response#response{refresh_token_expires_in = NewRefreshTokenExpiresIn}. -spec resource_owner(response()) -> {ok, term()}. resource_owner(#response{resource_owner = ResOwner}) -> {ok, ResOwner}. -spec resource_owner(response(), term()) -> response(). resource_owner(Response, NewResOwner) -> Response#response{resource_owner = NewResOwner}. -spec token_type(response()) -> {ok, binary()}. token_type(#response{}) -> {ok, ?TOKEN_TYPE}. -spec to_proplist(response()) -> proplists:proplist(). to_proplist(Response) -> response_foldr(Response, fun(Key, Value, Acc) -> [{Key, Value} | Acc] end, []). -ifndef(pre17). %-ifdef(pre18). %-spec to_map(response()) -> map(binary(), any()). %-else. %-spec to_map(response()) -> #{binary() => any()}. %-endif. to_map(Response) -> response_foldr(Response, fun(Key, Value, Acc) -> maps:put(Key, Value, Acc) end, maps:new()). -endif. %%%_* Private functions ================================================ -spec response_foldr(Response, Fun, Acc0) -> Return when Response :: response(), Fun :: fun((Key::binary(), Value::any(), Acc::any()) -> Acc::any()), Acc0 :: any(), Return :: any(). response_foldr(Record, Fun, Acc0) -> Keys = record_info(fields, response), Values = tl(tuple_to_list(Record)), %% Head is 'response'! response_foldr(Keys, Values, Fun, Acc0). response_foldr([], [], _Fun, Acc0) -> Acc0; response_foldr([_ | Ks], [undefined | Vs], Fun, Acc) -> response_foldr(Ks, Vs, Fun, Acc); response_foldr([refresh_token_expires_in | Ks], [V | Vs], Fun, Acc) -> Fun(<<"refresh_token_expires_in">>, V, response_foldr(Ks, Vs, Fun, Acc)); response_foldr([expires_in | Ks], [V | Vs], Fun, Acc) -> Fun(<<"expires_in">>, V, response_foldr(Ks, Vs, Fun, Acc)); response_foldr([K | Ks], [V | Vs], Fun, Acc) -> Key = atom_to_binary(K, latin1), Value = to_binary(V), Fun(Key, Value, response_foldr(Ks, Vs, Fun, Acc)). to_binary(Binary) when is_binary(Binary) -> Binary; to_binary([Binary]) when is_binary(Binary) -> Binary; to_binary([BinaryHead | Tail]) when is_binary(BinaryHead) -> <>; to_binary(List) when is_list(List) -> to_binary(list_to_binary(List)); to_binary(Atom) when is_atom(Atom) -> to_binary(atom_to_list(Atom)); to_binary(Float) when is_float(Float) -> to_binary(float_to_list(Float)); to_binary(Integer) when is_integer(Integer) -> to_binary(integer_to_list(Integer)); to_binary({Key, Value}) -> {to_binary(Key), to_binary(Value)}; to_binary(Term) -> to_binary(term_to_binary(Term)). %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/src/oauth2_priv_set.erl0000644000232200023220000001121014233730101020344 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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: p1_oauth2-0.6.11/src/oauth2.erl0000644000232200023220000004657514233730101016457 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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 %%% %%% This library is designed to simplify the implementation of the %%% server side of OAuth2 (http://tools.ietf.org/html/rfc6749). %%% @end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%_* Module declaration =============================================== -module(oauth2). -compile({no_auto_import, [get/2]}). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([authorize_password/3]). -export([authorize_password/4]). -export([authorize_password/5]). -export([authorize_client_credentials/3]). -export([authorize_code_grant/4]). -export([authorize_code_request/5]). -export([issue_code/2]). -export([issue_token/2]). -export([issue_token_and_refresh/2]). -export([verify_access_token/2]). -export([verify_access_code/2]). -export([verify_access_code/3]). -export([refresh_access_token/4]). -export_type([token/0]). -export_type([user/0]). -export_type([client/0]). -export_type([context/0]). -export_type([auth/0]). -export_type([lifetime/0]). -export_type([scope/0]). -export_type([appctx/0]). -export_type([error/0]). %%%_* Macros =========================================================== -define(BACKEND, (oauth2_config:backend())). -define(TOKEN, (oauth2_config:token_generation())). %%%_ * Types ----------------------------------------------------------- %% Opaque authentication record -record(a, { client = undefined :: undefined | term() , resowner = undefined :: undefined | term() , scope :: scope() , ttl = 0 :: non_neg_integer() }). -type context() :: proplists:proplist(). -type auth() :: #a{}. -type user() :: any(). %% Opaque User Object -type client() :: any(). %% Opaque Client Object -type rediruri() :: any(). %% Opaque Redirection URI -type token() :: binary(). -type response() :: oauth2_response:response(). -type lifetime() :: non_neg_integer(). -type scope() :: list(binary()) | binary(). -type appctx() :: term(). -type error() :: access_denied | invalid_client | invalid_grant | invalid_request | invalid_authorization | invalid_scope | unauthorized_client | unsupported_grant_type | unsupported_response_type | server_error | temporarily_unavailable | atom(). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Validates a request for an access token from resource owner's %% credentials. Use it to implement the following steps of RFC 6749: %% - 4.3.2. Resource Owner Password Credentials Grant > %% Access Token Request, when the client is public. -spec authorize_password(user(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_password(User, Scope, Ctx0) -> case auth_user(User, Scope, Ctx0) of {error, _}=E -> E; {ok, _}=Auth -> Auth end. %% @doc Validates a request for an access token from client and resource %% owner's credentials. Use it to implement the following steps of %% RFC 6749: %% - 4.3.2. Resource Owner Password Credentials Grant > %% Access Token Request, when the client is confidential. -spec authorize_password(user(), client(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_password(User, Client, Scope, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case auth_user(User, Scope, Ctx1) of {error, _} = E -> E; {ok, {Ctx2, Auth}} -> {ok, {Ctx2, Auth#a{client=C}}} end end. %% @doc Validates a request for an access token from client and resource %% owner's credentials. Use it to implement the following steps of %% RFC 6749: %% - 4.2.1. Implicit Grant > Authorization Request, when the client %% is public. -spec authorize_password(user(), client(), rediruri(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_password(User, Client, RedirUri, Scope, Ctx0) -> case ?BACKEND:get_client_identity(Client,Ctx0) of {error, _} ->{error, invalid_client}; {ok,{Ctx1,C}} -> case ?BACKEND:verify_redirection_uri(C, RedirUri, Ctx1) of {error, _} -> {error, invalid_client}; {ok, Ctx2} -> case auth_user(User, Scope, Ctx2) of {error, _} = E -> E; {ok, {Ctx3, Auth}} -> {ok, {Ctx3, Auth#a{client=C}}} end end end. %% @doc Validates a request for an access token from client's credentials. %% Use it to implement the following steps of RFC 6749: %% - 4.4.2. Client Credentials Grant > Access Token Request. -spec authorize_client_credentials(client(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_client_credentials(Client, Scope0, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case ?BACKEND:verify_client_scope(C, Scope0, Ctx1) of {error, _} -> {error, invalid_scope}; {ok, {Ctx2, Scope1}} -> {ok, {Ctx2, #a{ client=C , scope =Scope1 , ttl =oauth2_config:expiry_time( client_credentials) }}} end end. %% @doc Validates a request for an access token from an authorization code. %% Use it to implement the following steps of RFC 6749: %% - 4.1.3. Authorization Code Grant > Access Token Request. -spec authorize_code_grant(client(), binary(), rediruri(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_code_grant(Client, Code, RedirUri, Ctx0) -> case auth_client(Client, RedirUri, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case verify_access_code(Code, C, Ctx1) of {error, _}=E -> E; {ok, {Ctx2, GrantCtx}} -> {ok, Ctx3} = ?BACKEND:revoke_access_code(Code, Ctx2), {ok, {Ctx3, #a{ client =C , resowner=get_(GrantCtx,<<"resource_owner">>) , scope =get_(GrantCtx, <<"scope">>) , ttl =oauth2_config:expiry_time( password_credentials) }}} end end. %% @doc Validates a request for an authorization code from client and resource %% owner's credentials. Use it to implement the following steps of %% RFC 6749: %% - 4.1.1. Authorization Code Grant > Authorization Request. -spec authorize_code_request(user(), client(), rediruri(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_code_request(User, Client, RedirUri, Scope, Ctx0) -> case ?BACKEND:get_client_identity(Client, Ctx0) of {error, _} -> {error, unauthorized_client}; {ok, {Ctx1, C}} -> case ?BACKEND:verify_redirection_uri(C, RedirUri, Ctx1) of {error, _} -> {error, unauthorized_client}; {ok, Ctx2} -> case auth_user(User, Scope, Ctx2) of {error, _}=E -> E; {ok, {Ctx3, Auth}} -> {ok, { Ctx3 , Auth#a{ client=C , ttl =oauth2_config:expiry_time( code_grant) } }} end end end. %% @doc Issues an authorization code from an authorization. Use it to implement %% the following steps of RFC 6749: %% - 4.1.2. Authorization Code Grant > Authorization Response, with the %% result of authorize_code_request/6. -spec issue_code(auth(), appctx()) -> {ok, {appctx(), response()}}. issue_code(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) -> GrantContext = build_context(Client, seconds_since_epoch(TTL), Owner, Scope), AccessCode = ?TOKEN:generate(GrantContext), {ok, Ctx1} = ?BACKEND:associate_access_code(AccessCode,GrantContext,Ctx0), {ok, {Ctx1, oauth2_response:new(<<>>,TTL,Owner,Scope,<<>>,<<>>,AccessCode)}}. %% @doc Issues an access token without refresh token from an authorization. %% Use it to implement the following steps of RFC 6749: %% - 4.1.4. Authorization Code Grant > Authorization Response, with the %% result of authorize_code_grant/5 when no refresh token must be issued. %% - 4.2.2. Implicit Grant > Access Token Response, with the result of %% authorize_password/7. %% - 4.3.3. Resource Owner Password Credentials Grant > %% Access Token Response, with the result of authorize_password/4 or %% authorize_password/6 when the client is public or no refresh token %% must be issued. %% - 4.4.3. Client Credentials Grant > Access Token Response, with the %% result of authorize_client_credentials/4. -spec issue_token(auth(), appctx()) -> {ok, {appctx(), response()}}. issue_token(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) -> GrantContext = build_context(Client,seconds_since_epoch(TTL),Owner,Scope), AccessToken = ?TOKEN:generate(GrantContext), {ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken , GrantContext , Ctx0 ), {ok, {Ctx1, oauth2_response:new(AccessToken, TTL, Owner, Scope)}}. %% @doc Issues access and refresh tokens from an authorization. %% Use it to implement the following steps of RFC 6749: %% - 4.1.4. Authorization Code Grant > Access Token Response, with the %% result of authorize_code_grant/5 when a refresh token must be issued. %% - 4.3.3. Resource Owner Password Credentials Grant > %% Access Token Response, with the result of authorize_password/6 when %% the client is confidential and a refresh token must be issued. -spec issue_token_and_refresh(auth(), appctx()) -> {ok, {appctx(), response()}} | {error, invalid_authorization}. issue_token_and_refresh(#a{client = undefined}, _Ctx) -> {error, invalid_authorization}; issue_token_and_refresh(#a{resowner = undefined}, _Ctx) -> {error, invalid_authorization}; issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL} , Ctx0 ) -> RTTL = oauth2_config:expiry_time(refresh_token), RefreshCtx = build_context(Client,seconds_since_epoch(RTTL),Owner,Scope), RefreshToken = ?TOKEN:generate(RefreshCtx), AccessCtx = build_context(Client,seconds_since_epoch(TTL),Owner,Scope,RefreshToken), AccessToken = ?TOKEN:generate(AccessCtx), {ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken , AccessCtx , Ctx0), {ok, Ctx2} = ?BACKEND:associate_refresh_token( RefreshToken , RefreshCtx , Ctx1 ), {ok, {Ctx2, oauth2_response:new( AccessToken , TTL , Owner , Scope , RefreshToken , RTTL )}}. %% @doc Verifies an access code AccessCode, returning its associated %% context if successful. Otherwise, an OAuth2 error code is returned. -spec verify_access_code(token(), appctx()) -> {ok, {appctx(), context()}} | {error, error()}. verify_access_code(AccessCode, Ctx0) -> case ?BACKEND:resolve_access_code(AccessCode, Ctx0) of {error, _} -> {error, invalid_grant}; {ok, {Ctx1, GrantCtx}} -> case get_(GrantCtx, <<"expiry_time">>) > seconds_since_epoch(0) of true -> {ok, {Ctx1, GrantCtx}}; false -> ?BACKEND:revoke_access_code(AccessCode, Ctx1), {error, invalid_grant} end end. %% @doc Verifies an access code AccessCode and it's corresponding Identity, %% returning its associated context if successful. Otherwise, an OAuth2 %% error code is returned. -spec verify_access_code(token(), client(), appctx()) -> {ok, {appctx(), context()}} | {error, error()}. verify_access_code(AccessCode, Client, Ctx0) -> case verify_access_code(AccessCode, Ctx0) of {error, _}=E -> E; {ok, {Ctx1, GrantCtx}} -> case get(GrantCtx, <<"client">>) of {ok, Client} -> {ok, {Ctx1, GrantCtx}}; _ -> {error, invalid_grant} end end. %% @doc Validates a request for an access token from a refresh token, issuing %% a new access token if valid. Use it to implement the following steps of %% RFC 6749: %% - 6. Refreshing an Access Token. -spec refresh_access_token(client(), token(), scope(), appctx()) -> {ok, {appctx(), response()}} | {error, error()}. refresh_access_token(Client, RefreshToken, Scope, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case ?BACKEND:resolve_refresh_token(RefreshToken, Ctx1) of {error, _} -> {error, invalid_grant}; {ok, {Ctx2, GrantCtx}} -> {ok, ExpiryAbsolute} = get(GrantCtx, <<"expiry_time">>), case ExpiryAbsolute > seconds_since_epoch(0) of true -> {ok, C} = get(GrantCtx, <<"client">>), {ok, RegScope} = get(GrantCtx, <<"scope">>), case ?BACKEND:verify_scope( RegScope , Scope , Ctx2) of {error, _} -> {error, invalid_scope}; {ok, {Ctx3, VerScope}} -> {ok, ResOwner} = get( GrantCtx , <<"resource_owner">> ), TTL = oauth2_config:expiry_time( password_credentials), issue_token(#a{ client = C , resowner = ResOwner , scope = VerScope , ttl = TTL }, Ctx3) end; false -> ?BACKEND:revoke_refresh_token(RefreshToken, Ctx2), {error, invalid_grant} end end end. %% @doc Verifies an access token AccessToken, returning its associated %% context if successful. Otherwise, an OAuth2 error code is returned. -spec verify_access_token(token(), appctx()) -> {ok, {appctx(), context()}} | {error, error()}. verify_access_token(AccessToken, Ctx0) -> case ?BACKEND:resolve_access_token(AccessToken, Ctx0) of {error, _} -> {error, access_denied}; {ok, {Ctx1, GrantCtx}} -> case get_(GrantCtx, <<"expiry_time">>) > seconds_since_epoch(0) of true -> {ok, {Ctx1, GrantCtx}}; false -> ?BACKEND:revoke_access_token(AccessToken, Ctx1), {error, access_denied} end end. %%%_* Private functions ================================================ auth_user(User, Scope0, Ctx0) -> case ?BACKEND:authenticate_user(User, Ctx0) of {error, _}=E -> E; {ok, {Ctx1, Owner}} -> case ?BACKEND:verify_resowner_scope(Owner, Scope0, Ctx1) of {error, _} -> {error, invalid_scope}; {ok, {Ctx2, Scope1}} -> {ok, {Ctx2, #a{ resowner = Owner , scope = Scope1 , ttl = oauth2_config:expiry_time( password_credentials) }}} end end. auth_client(Client, no_redir, Ctx0) -> ?BACKEND:authenticate_client(Client, Ctx0); auth_client(Client, RedirUri, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _}=E -> E; {ok, {Ctx1, C}} -> case ?BACKEND:verify_redirection_uri(C, RedirUri, Ctx1) of {error, _} -> {error, invalid_grant}; {ok, Ctx2} -> {ok, {Ctx2, C}} end end. -spec build_context(term(), non_neg_integer(), term(), scope()) -> context(). build_context(Client, ExpiryTime, ResOwner, Scope) -> [ {<<"client">>, Client} , {<<"resource_owner">>, ResOwner} , {<<"expiry_time">>, ExpiryTime} , {<<"scope">>, Scope} ]. build_context(Client, ExpiryTime, ResOwner, Scope, RefreshToken) -> [{<<"refresh_token">>, RefreshToken} | build_context(Client, ExpiryTime, ResOwner, Scope)]. -spec seconds_since_epoch(integer()) -> non_neg_integer(). seconds_since_epoch(Diff) -> {Mega, Secs, _} = os:timestamp(), Mega * 1000000 + Secs + Diff. get(O, K) -> case lists:keyfind(K, 1, O) of {K, V} -> {ok, V}; false -> {error, notfound} end. get_(O, K) -> {ok, V} = get(O, K), V. %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/src/oauth2_token.erl0000644000232200023220000000522514233730101017642 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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_token). -behaviour(oauth2_token_generation). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([generate/1]). %%%_* Macros =========================================================== -define(TOKEN_LENGTH, 32). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Generates a random OAuth2 token. -spec generate(oauth2:context()) -> oauth2:token(). generate(_Context) -> generate_fragment(?TOKEN_LENGTH). %%%_* Private functions ================================================ -spec generate_fragment(integer()) -> binary(). generate_fragment(0) -> <<>>; generate_fragment(N) -> Rand = base64:encode(crypto:strong_rand_bytes(N)), Frag = << <> || <> <= <>, is_alphanum(C) >>, <>. %% @doc Returns true for alphanumeric ASCII characters, false for all others. -spec is_alphanum(char()) -> boolean(). is_alphanum(C) when C >= 16#30 andalso C =< 16#39 -> true; is_alphanum(C) when C >= 16#41 andalso C =< 16#5A -> true; is_alphanum(C) when C >= 16#61 andalso C =< 16#7A -> true; is_alphanum(_) -> false. %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/src/oauth2_backend.erl0000644000232200023220000001324214233730101020107 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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_backend). %%%_ * Types ----------------------------------------------------------- -type grantctx() :: oauth2:context(). -type appctx() :: oauth2:appctx(). -type token() :: oauth2:token(). -type scope() :: oauth2:scope(). -type user() :: oauth2:user(). -type client() :: oauth2:client(). %%%_* Behaviour ======================================================== %% doc Authenticates a combination of username and password. %% Returns the resource owner identity if the credentials are valid. -callback authenticate_user(user(), appctx()) -> {ok, {appctx(), term()}} | {error, atom()}. %% doc Authenticates a client's credentials for a given scope. -callback authenticate_client(client(), appctx()) -> {ok, {appctx(), client()}} | {error, notfound | badsecret}. %% doc Stores a new access code token(), associating it with Context. %% The context is a proplist carrying information about the identity %% with which the code is associated, when it expires, etc. -callback associate_access_code(token(), grantctx(), appctx()) -> {ok, appctx()} | {error, notfound}. %% doc Stores a new access token token(), associating it with Context. %% The context is a proplist carrying information about the identity %% with which the token is associated, when it expires, etc. -callback associate_access_token(token(), grantctx(), appctx()) -> {ok, appctx()} | {error, notfound}. %% doc Stores a new refresh token token(), associating it with %% grantctx(). The context is a proplist carrying information about the %% identity with which the token is associated, when it expires, etc. -callback associate_refresh_token(token(), grantctx(), appctx()) -> {ok, appctx()} | {error, notfound}. %% doc Looks up an access token token(), returning the corresponding %% context if a match is found. -callback resolve_access_token(token(), appctx()) -> {ok, {appctx(), grantctx()}} | {error, notfound}. %% doc Looks up an access code token(), returning the corresponding %% context if a match is found. -callback resolve_access_code(token(), appctx()) -> {ok, {appctx(), grantctx()}} | {error, notfound}. %% doc Looks up an refresh token token(), returning the corresponding %% context if a match is found. -callback resolve_refresh_token(token(), appctx()) -> {ok, {appctx(), grantctx()}} | {error, notfound}. %% doc Revokes an access token token(), so that it cannot be used again. -callback revoke_access_token(token(), appctx()) -> {ok, appctx()} | {error, notfound}. %% doc Revokes an access code token(), so that it cannot be used again. -callback revoke_access_code(token(), appctx()) -> {ok, appctx()} | {error, notfound}. %% doc Revokes an refresh token token(), so that it cannot be used again. -callback revoke_refresh_token(token(), appctx()) -> {ok, appctx()} | {error, notfound}. %% doc Returns a client identity for a given id. -callback get_client_identity(client(), appctx()) -> {ok, {appctx(), client()}} | {error, notfound | badsecret}. %% doc Verifies that RedirectionUri is a valid redirection URI for the %% client identified by Identity. -callback verify_redirection_uri(client(), binary(), appctx()) -> {ok, appctx()} | {error, notfound | baduri}. %% doc Verifies that scope() is a valid scope for the client identified %% by Identity. -callback verify_client_scope(client(), scope(), appctx()) -> {ok, {appctx(), scope()}} | {error, notfound | badscope}. %% doc Verifies that scope() is a valid scope for the resource %% owner identified by Identity. -callback verify_resowner_scope(term(), scope(), appctx()) -> {ok, {appctx(), scope()}} | {error, notfound | badscope}. %% doc Verifies that scope() is a valid scope of the set of scopes defined %% by Validscope()s. -callback verify_scope(scope(), scope(), appctx()) -> {ok, {appctx(), scope()}} | {error, notfound | badscope}. %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/src/oauth2_token_generation.erl0000644000232200023220000000275714233730101022064 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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_token_generation). %%%_* Behaviour ======================================================== -callback generate(oauth2:context()) -> oauth2:token(). %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/src/oauth2_config.erl0000644000232200023220000000624314233730101017770 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% 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_config). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([backend/0]). -export([expiry_time/0]). -export([expiry_time/1]). -export([token_generation/0]). %%%_* Macros =========================================================== %% Default time in seconds before an authentication token expires. -define(DEFAULT_TOKEN_EXPIRY, 3600). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Gets the default expiry time for access tokens. -spec expiry_time() -> non_neg_integer(). expiry_time() -> get_optional(expiry_time, ?DEFAULT_TOKEN_EXPIRY). %% @doc Gets a specific expiry time for access tokens if available %% returns the default if non found -spec expiry_time(atom()) -> non_neg_integer(). expiry_time(Flow) -> case application:get_env(oauth2, Flow) of undefined -> expiry_time(); {ok, Value} -> case lists:keyfind(expiry_time, 1, Value) of false -> expiry_time(); {_Key, Val} -> Val end end. %% @doc Gets the backend for validating passwords, storing tokens, etc. -spec backend() -> atom(). backend() -> get_required(backend). %% @doc Gets the backend for generating tokens. -spec token_generation() -> atom(). token_generation() -> get_optional(token_generation, oauth2_token). %%%_* Private functions ================================================ get_optional(Key, Default) -> case application:get_env(oauth2, Key) of undefined -> Default; {ok, Value} -> Value end. get_required(Key) -> case application:get_env(oauth2, Key) of undefined -> throw({missing_config, Key}); {ok, Value} -> Value end. %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/src/p1_oauth2.app.src0000644000232200023220000000262614233730101017630 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% {application, p1_oauth2, [ {description, "Erlang OAuth 2.0 implementation"}, {vsn, "0.6.11"}, {registered, []}, {applications, [ kernel, stdlib ]}, {env, []}, %% hex.pm packaging: {licenses, ["MIT", "Apache 2.0"]}, {links, [{"Github", "https://github.com/processone/p1_oauth2"}]}]}. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.11/rebar.config0000644000232200023220000000343414233730101016227 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 Kivra %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% %% ---------------------------------------------------------------------------- {erl_opts, [debug_info, {platform_define, "^R", pre17}, {platform_define, "^(R|17)", pre18} ]}. {cover_enabled, true}. {cover_export_enabled, true}. {coveralls_coverdata , "_build/test/cover/eunit.coverdata"}. {coveralls_service_name , "github"}. {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.11/test/0000755000232200023220000000000014233730101014720 5ustar debalancedebalancep1_oauth2-0.6.11/test/oauth2_tests.erl0000644000232200023220000004626114233730101020061 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 Kivra %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% %% ---------------------------------------------------------------------------- -module(oauth2_tests). -include_lib("eunit/include/eunit.hrl"). %%% Placeholder values that the mock backend will recognize. -define(USER_NAME, <<"herp">>). -define(USER_PASSWORD, <<"derp">>). -define(USER_SCOPE, [<<"xyz">>]). -define(RESOURCE_OWNER, <<"user">>). -define(CLIENT_ID, <<"TiaUdYODLOMyLkdaKkqlmhsl9QJ94a">>). -define(CLIENT_SECRET, <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">>). -define(CLIENT_SCOPE, <<"abc">>). -define(CLIENT_URI, <<"https://no.where/cb">>). %%%=================================================================== %%% Test cases %%%=================================================================== bad_authorize_password_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ ?_assertMatch({ok, _}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, [<<"xyz">>], foo_context)), ?_assertMatch({error, invalid_scope}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, <<"bad_scope">>, foo_context)), ?_assertMatch({error, badpass}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, <<"xyz">>, foo_context)), ?_assertMatch({error, notfound}, oauth2:authorize_password( {<<"derp">>,<<"derp">>}, <<"xyz">>, foo_context)), ?_assertMatch({ok, _}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, {?CLIENT_ID,?CLIENT_SECRET}, [<<"xyz">>], foo_context)), ?_assertMatch({error, invalid_scope}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, {?CLIENT_ID, ?CLIENT_SECRET}, <<"bad_scope">>, foo_context)), ?_assertMatch({error, badpass}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, {?CLIENT_ID, ?CLIENT_SECRET}, <<"xyz">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, {?CLIENT_ID, <<"gggDMAwklAKc9kq5FsLjKrzi">>}, <<"xyz">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, {<<"XoaUdYODRC">>, ?CLIENT_SECRET}, <<"xyz">>, foo_context)) ] end}. authorize_implicit_grant_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, {foo_context, Auth}} = oauth2:authorize_password( {?USER_NAME,?USER_PASSWORD} , ?CLIENT_ID , ?CLIENT_URI , ?USER_SCOPE , foo_context), {ok, {foo_context, Response}} = oauth2:issue_token(Auth, foo_context), {ok, Token} = oauth2_response:access_token(Response), ?assertMatch( {ok, _} , oauth2:verify_access_token( Token , foo_context )) end ] end}. bad_authorize_client_credentials_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ ?_assertMatch({error, invalid_client}, oauth2:authorize_client_credentials( { <<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">> , <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">> }, <<"abc">>, foo_context)), ?_assertMatch({error, invalid_scope}, oauth2:authorize_client_credentials( {?CLIENT_ID, ?CLIENT_SECRET}, <<"bad_scope">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_client_credentials( { <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">> , <<"gggDMAwklAKc9kq5FsLjKrzihCcI123">> }, <<"abc">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_client_credentials( { <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">> , <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">> }, <<"cba">>, foo_context)) ] end}. bad_ttl_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> application:set_env(oauth2, expiry_time, 0), {ok, Response} = issue_access_token(foo_context), {ok, Token} = oauth2_response:access_token(Response), ?assertEqual({error, access_denied}, oauth2:verify_access_token(Token, foo_context)) end, fun() -> application:set_env(oauth2, expiry_time, 0), {ok, Response} = issue_access_code(foo_context), {ok, Code} = oauth2_response:access_code(Response), ?assertEqual({error, invalid_grant}, oauth2:verify_access_code(Code, foo_context)) end, fun() -> application:set_env(oauth2, expiry_time, 3600), {ok, Res1} = issue_access_code(foo_context), application:set_env(oauth2, expiry_time, 0), {ok, Res2} = issue_token_and_refresh(Res1, foo_context), {ok, RefreshToken} = oauth2_response:refresh_token(Res2), ?assertEqual({error, invalid_grant}, oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, ?USER_SCOPE, foo_context)) end ] end}. verify_access_token_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, Response} = issue_access_token(foo_context), {ok, Token} = oauth2_response:access_token(Response), ?assertMatch({ok, {foo_context, _}}, oauth2:verify_access_token(Token, foo_context)) end, ?_assertMatch({error, access_denied}, oauth2:verify_access_token(<<"nonexistent_token">>, foo_context)) ] end}. bad_access_code_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {error, unauthorized_client} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, <<"http://in.val.id">>, ?USER_SCOPE, foo_context), {error, unauthorized_client} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, <<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>, ?CLIENT_URI, ?CLIENT_SCOPE, foo_context), {error, invalid_scope} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, ?CLIENT_URI, <<"bad_scope">>, foo_context), {error, badpass} = oauth2:authorize_code_request( {<<"herp">>, <<"herp">>}, ?CLIENT_ID, ?CLIENT_URI, ?CLIENT_SCOPE, foo_context), ?_assertMatch({error, invalid_grant}, oauth2:verify_access_code(<<"nonexistent_token">>, foo_context)) end ] end}. verify_access_code_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, Response} = issue_access_code(foo_context), {ok, Code} = oauth2_response:access_code(Response), ?assertMatch({ok, {user, 31337}}, oauth2_response:resource_owner(Response)), ?assertMatch({ok, _}, oauth2:verify_access_code( Code, foo_context)), {ok, {foo_context, Auth2}} = oauth2:authorize_code_grant( {?CLIENT_ID, ?CLIENT_SECRET}, Code, ?CLIENT_URI, foo_context), {ok, {foo_context, Response2}} = oauth2:issue_token_and_refresh(Auth2, foo_context), {ok, Token} = oauth2_response:access_token(Response2), ?assertMatch({ok, _}, oauth2:verify_access_token( Token, foo_context)) end ] end}. bad_refresh_token_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, {foo_context, Auth}} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, ?CLIENT_URI, ?USER_SCOPE, foo_context), {ok, {foo_context, Response}} = oauth2:issue_code(Auth, foo_context), {ok, Code} = oauth2_response:access_code(Response), {ok, {foo_context, Auth2}} = oauth2:authorize_code_grant( {?CLIENT_ID, ?CLIENT_SECRET}, Code, ?CLIENT_URI, foo_context), {ok, {foo_context, Res2}} = oauth2:issue_token_and_refresh(Auth2, foo_context), {ok, RefreshToken} = oauth2_response:refresh_token(Res2), ?assertMatch({error, invalid_client}, oauth2:refresh_access_token( {<<"foo">>, ?CLIENT_SECRET}, RefreshToken, ?CLIENT_SCOPE, foo_context)), ?assertMatch({error, invalid_client}, oauth2:refresh_access_token( {?CLIENT_ID, <<"foo">>}, RefreshToken, ?CLIENT_SCOPE, foo_context)), ?assertMatch({error, invalid_grant}, oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, <<"foo">>, ?CLIENT_SCOPE, foo_context)), ?assertMatch({error, invalid_scope}, oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, <<"foo">>, foo_context)) end ] end}. verify_refresh_token_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, Res1} = issue_access_code(foo_context), {ok, Res2} = issue_token_and_refresh(Res1, foo_context), {ok, RefreshToken} = oauth2_response:refresh_token(Res2), {ok, _} = oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, ?USER_SCOPE, foo_context), {ok, Token} = oauth2_response:access_token(Res2), ?assertMatch({ok, _}, oauth2:verify_access_token( Token, foo_context)) end, fun() -> lists:foreach(fun(UserNameAndPasswordStrategyFun) -> {ok, Response} = issue_token_and_refresh_with_user_name_and_password( foo_context, UserNameAndPasswordStrategyFun), {ok, RefreshToken} = oauth2_response:refresh_token(Response), {ok, {foo_context, Response2}} = oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, ?USER_SCOPE, foo_context), {ok, NewAccessToken} = oauth2_response:access_token(Response2), ?assertMatch({ok, _}, oauth2:verify_access_token( NewAccessToken, foo_context)) end, [ fun(Context) -> oauth2:authorize_password( {?USER_NAME, ?USER_PASSWORD}, {?CLIENT_ID, ?CLIENT_SECRET}, ?USER_SCOPE, Context) end ]) end ] end}. %%%=================================================================== %%% Setup/teardown %%%=================================================================== start() -> application:set_env(oauth2, backend, oauth2_mock_backend), application:set_env(oauth2, expiry_time, 3600), oauth2_mock_backend:start(), ok. stop(_State) -> oauth2_mock_backend:stop(), ok. %%%=================================================================== %%% Helpers %%%=================================================================== issue_access_token(Context) -> {ok, {Context, Authorization}} = oauth2:authorize_client_credentials( {?CLIENT_ID, ?CLIENT_SECRET}, ?CLIENT_SCOPE, Context), {ok, {Context, Response}} = oauth2:issue_token(Authorization, Context), {ok, Response}. issue_access_code(Context) -> {ok, {Context, Auth}} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, ?CLIENT_URI, ?USER_SCOPE, Context), {ok, {Context, Response}} = oauth2:issue_code(Auth, Context), {ok, Response}. issue_token_and_refresh(Response, Context) -> {ok, Code} = oauth2_response:access_code(Response), {ok, {Context, Auth2}} = oauth2:authorize_code_grant( {?CLIENT_ID, ?CLIENT_SECRET}, Code, ?CLIENT_URI, Context), {ok, {Context, Res2}} = oauth2:issue_token_and_refresh(Auth2, Context), {ok, Res2}. issue_token_and_refresh_with_user_name_and_password( Context, UserNameAndPasswordStrategyFun) -> {ok, {Context, Auth}} = UserNameAndPasswordStrategyFun(Context), {ok, {Context, Response}} = oauth2:issue_token_and_refresh(Auth, Context), {ok, Response}. p1_oauth2-0.6.11/test/oauth2_response_tests.erl0000644000232200023220000002050614233730101021771 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 Kivra %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% %% ---------------------------------------------------------------------------- -module(oauth2_response_tests). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(ACCESS, <<"9bX9iFUOsXbM12OOjfDW175IXXOELp6K">>). -define(REFRESH, <<"JVs3ZFQJBIdduJdhhWOoAt2B3qEKcHEo">>). -define(CODE, <<"Lz7Z24cKSQ28z8kem01ZP9c0aE3TEbGl">>). -define(RESOURCE_OWNER, <<"user">>). -define(EXPIRY, 3600). -define(SCOPE, <<"herp derp">>). -define(TOKEN_TYPE, <<"bearer">>). %%%=================================================================== %%% Test cases %%%=================================================================== proper_type_spec_test_() -> {timeout, 1200, [{?LINE, fun() -> proper:check_specs(oauth2_response, [{to_file, user}]) end}]}. new_1_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertMatch({error, not_set}, oauth2_response:expires_in(Response)), ?_assertMatch({error, not_set}, oauth2_response:scope(Response)), ?_assertMatch({error, not_set}, oauth2_response:refresh_token(Response)) ] end}. new_2_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertMatch({error, not_set}, oauth2_response:scope(Response)), ?_assertMatch({error, not_set}, oauth2_response:refresh_token(Response)) ] end}. new_4_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY, ?RESOURCE_OWNER, ?SCOPE) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertEqual({ok, ?SCOPE}, oauth2_response:scope(Response)), ?_assertMatch({error, not_set}, oauth2_response:refresh_token(Response)) ] end}. new_5_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY, ?RESOURCE_OWNER, ?SCOPE, ?REFRESH, ?EXPIRY) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertEqual({ok, ?SCOPE}, oauth2_response:scope(Response)), ?_assertEqual({ok, ?REFRESH}, oauth2_response:refresh_token(Response)) ] end}. new_6_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY, ?RESOURCE_OWNER, ?SCOPE, ?REFRESH, ?EXPIRY, ?CODE) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({error, not_set}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertEqual({ok, ?SCOPE}, oauth2_response:scope(Response)), ?_assertEqual({error, not_set}, oauth2_response:refresh_token(Response)), ?_assertEqual({ok, ?CODE}, oauth2_response:access_code(Response)) ] end}. access_token_test() -> ?assertEqual({ok, ?REFRESH}, oauth2_response:access_token( oauth2_response:access_token( oauth2_response:new(?ACCESS), ?REFRESH))). access_code_test() -> ?assertEqual({ok, ?CODE}, oauth2_response:access_code( oauth2_response:access_code( oauth2_response:new([], [], [], [], [], ?CODE), ?CODE))). expires_in_test() -> ?assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in( oauth2_response:expires_in( oauth2_response:new(?ACCESS), ?EXPIRY))). scope_test() -> ?assertEqual({ok, ?SCOPE}, oauth2_response:scope( oauth2_response:scope( oauth2_response:new(?ACCESS), ?SCOPE))). refresh_token_test() -> ?assertEqual({ok, ?REFRESH}, oauth2_response:refresh_token( oauth2_response:refresh_token( oauth2_response:new(?ACCESS), ?REFRESH))). resource_owner_test() -> ?assertEqual({ok, ?RESOURCE_OWNER}, oauth2_response:resource_owner( oauth2_response:resource_owner( oauth2_response:new(?ACCESS), ?RESOURCE_OWNER))). token_type_test() -> ?assertEqual({ok, ?TOKEN_TYPE}, oauth2_response:token_type(oauth2_response:new(?ACCESS))). to_proplist_test() -> Property = ?FORALL( {AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry}, {non_empty(oauth2:token()), oauth2:lifetime(), binary(), oauth2:scope(), non_empty(oauth2:token()), oauth2:lifetime()}, begin Response = oauth2_response:new(AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry), [ {<<"access_token">>, AccessToken}, {<<"expires_in">>, Expiry}, {<<"resource_owner">>, ResourceOwner}, {<<"scope">>, scope_to_binary(Scope)}, {<<"refresh_token">>, RefreshToken}, {<<"refresh_token_expires_in">>, RefreshExpiry}, {<<"token_type">>, <<"bearer">>} ] =:= oauth2_response:to_proplist(Response) end), ?assert(proper:quickcheck(Property, [{to_file, user}])). -ifndef(pre17). to_map_test() -> Property = ?FORALL( {AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry}, {non_empty(oauth2:token()), oauth2:lifetime(), binary(), oauth2:scope(), non_empty(oauth2:token()), oauth2:lifetime()}, begin Response = oauth2_response:new(AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry), #{ <<"access_token">> => AccessToken, <<"expires_in">> => Expiry, <<"resource_owner">> => ResourceOwner, <<"scope">> => scope_to_binary(Scope), <<"refresh_token">> => RefreshToken, <<"refresh_token_expires_in">> => RefreshExpiry, <<"token_type">> => <<"bearer">> } =:= oauth2_response:to_map(Response) end), ?assert(proper:quickcheck(Property, [{to_file, user}])). -endif. %%%=================================================================== %%% Helpers %%%=================================================================== scope_to_binary(Binary) when is_binary(Binary) -> Binary; scope_to_binary([]) -> <<>>; scope_to_binary([Binary]) when is_binary(Binary) -> Binary; scope_to_binary([BinaryHead | Tail]) when is_binary(BinaryHead) -> <>. p1_oauth2-0.6.11/test/oauth2_token_tests.erl0000644000232200023220000000466614233730101021264 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 Kivra %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% %% ---------------------------------------------------------------------------- -module(oauth2_token_tests). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). %%%=================================================================== %%% Test cases %%%=================================================================== proper_type_spec_test_() -> {timeout, 1200, [{?LINE, fun() -> proper:check_specs(oauth2_token, [{to_file, user}]) end}]}. generate_test() -> Token = oauth2_token:generate([]), ?assertEqual(byte_size(Token), 32), ?assert(lists:all(fun is_alphanum/1, binary_to_list(Token))). %%%=================================================================== %%% Utility functions %%%=================================================================== %% @doc Returns true for alphanumeric ASCII characters, false for all others. -spec is_alphanum(Char :: char()) -> boolean(). is_alphanum(C) when C >= 16#30 andalso C =< 16#39 -> true; is_alphanum(C) when C >= 16#41 andalso C =< 16#5A -> true; is_alphanum(C) when C >= 16#61 andalso C =< 16#7A -> true; is_alphanum(_) -> false. p1_oauth2-0.6.11/test/oauth2_priv_set_tests.erl0000644000232200023220000001012214233730101021757 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 Kivra %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% %% ---------------------------------------------------------------------------- -module(oauth2_priv_set_tests). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). %%%=================================================================== %%% Test cases %%%=================================================================== proper_type_spec_test_() -> {timeout, 1200, [{?LINE, fun() -> proper:check_specs(oauth2_priv_set, [{to_file, user}]) end}]}. new_test_() -> [ ?_assert(oauth2_priv_set:is_member( <<"x.y.z">>, oauth2_priv_set:new(<<"x.y.z">>))), ?_assert(oauth2_priv_set:is_member( <<"a.b.c">>, oauth2_priv_set:new([<<"a.b.c">>, <<"a.b.d">>]))), ?_assertNot(oauth2_priv_set:is_member( <<"a.b.c">>, oauth2_priv_set:new(<<"x.y.z">>))), ?_assertNot(oauth2_priv_set:is_member( <<"a.b.e">>, oauth2_priv_set:new([<<"a.b.c">>, <<"a.b.a">>]))) ]. is_subset_test_() -> [ ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.b.c.d.e">>), oauth2_priv_set:new([ <<"a.b">>, <<"a.b.x.y">>, <<"a.b.z.x">>, <<"a.b.k.d.g.e">>, <<"a.b.m.n.p.q">>, <<"a.b.c.d.*">> ]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.b.c">>), oauth2_priv_set:new([<<"a.b.c">>, <<"a.s.d.f.g.h">>]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.y.z">>), oauth2_priv_set:new([<<"x.y">>, <<"x.*">>]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.y.z">>), oauth2_priv_set:new([<<"x.*">>, <<"x.y">>]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.*">>), oauth2_priv_set:new([<<"a.*">>, <<"x.*">>]))), ?_assertNot(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.b.c">>), oauth2_priv_set:new(<<"a.b">>))), ?_assertNot(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.y.z">>), oauth2_priv_set:new(<<"x.z.*">>))), ?_assertNot(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.*">>), oauth2_priv_set:new([<<"a.b.*">>, <<"a.c.*">>, <<"a.d.*">>, <<"a.e.*">>, <<"a.f.z">>]))) ]. p1_oauth2-0.6.11/test/oauth2_mock_backend.erl0000644000232200023220000001257314233730101021316 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 Kivra %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% %% ---------------------------------------------------------------------------- -module(oauth2_mock_backend). -behavior(oauth2_backend). %%% Behavior API -export([authenticate_user/2]). -export([authenticate_client/2]). -export([get_client_identity/2]). -export([associate_access_code/3]). -export([associate_refresh_token/3]). -export([associate_access_token/3]). -export([resolve_access_code/2]). -export([resolve_refresh_token/2]). -export([resolve_access_token/2]). -export([revoke_access_code/2]). -export([revoke_access_token/2]). -export([revoke_refresh_token/2]). -export([get_redirection_uri/2]). -export([verify_redirection_uri/3]). -export([verify_client_scope/3]). -export([verify_resowner_scope/3]). -export([verify_scope/3]). %%% mock_backend-specifics -export([start/0]). -export([stop/0]). %%% Placeholder values that the mock backend will recognize. -define(UNAME, <<"herp">>). -define(PASSWORD, <<"derp">>). -define(USCOPE, [<<"xyz">>]). -define(RES_OWNER, <<"user">>). -define(CID, <<"TiaUdYODLOMyLkdaKkqlmhsl9QJ94a">>). -define(SECRET, <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">>). -define(CSCOPE, <<"abc">>). -define(CLIENT_URI, <<"https://no.where/cb">>). -define(ETS_TABLE, access_tokens). %%%=================================================================== %%% API %%%=================================================================== authenticate_user({?UNAME, ?PASSWORD}, Ctx) -> {ok, {Ctx, {user, 31337}}}; authenticate_user({?UNAME, _}, _) -> {error, badpass}; authenticate_user(_, _) -> {error, notfound}. authenticate_client({?CID, ?SECRET}, Ctx) -> {ok, {Ctx, {client, 4711}}}; authenticate_client({?CID, _}, _) -> {error, badsecret}; authenticate_client(_, _) -> {error, notfound}. get_client_identity(?CID, Ctx) -> {ok, {Ctx, {client, 4711}}}; get_client_identity(_, _) -> {error, notfound}. associate_access_code(AccessCode, Context, AppContext) -> associate_access_token(AccessCode, Context, AppContext). associate_refresh_token(RefreshToken, Context, AppContext) -> ets:insert(?ETS_TABLE, {RefreshToken, Context}), {ok, AppContext}. associate_access_token(AccessToken, Context, AppContext) -> ets:insert(?ETS_TABLE, {AccessToken, Context}), {ok, AppContext}. resolve_access_code(AccessCode, AppContext) -> resolve_access_token(AccessCode, AppContext). resolve_refresh_token(RefreshToken, AppContext) -> resolve_access_token(RefreshToken, AppContext). resolve_access_token(AccessToken, AppContext) -> case ets:lookup(?ETS_TABLE, AccessToken) of [] -> {error, notfound}; [{_, Context}] -> {ok, {AppContext, Context}} end. revoke_access_code(AccessCode, AppContext) -> revoke_access_token(AccessCode, AppContext). revoke_access_token(AccessToken, AppContext) -> ets:delete(?ETS_TABLE, AccessToken), {ok, AppContext}. revoke_refresh_token(_RefreshToken, AppContext) -> {ok, AppContext}. get_redirection_uri({?CID, ?SECRET}, Ctx) -> {ok, {Ctx, ?CLIENT_URI}}; get_redirection_uri(_, _) -> {error, notfound}. verify_redirection_uri({client, 4711}, ?CLIENT_URI, Ctx) -> {ok, Ctx}; verify_redirection_uri(_, _, _) -> {error, mismatch}. verify_client_scope({client, 4711}, [], Ctx) -> {ok, {Ctx, []}}; verify_client_scope({client, 4711}, ?CSCOPE, Ctx) -> {ok, {Ctx, ?CSCOPE}}; verify_client_scope(_, _, _) -> {error, invalid_scope}. verify_resowner_scope({user, 31337}, ?USCOPE, Ctx) -> {ok, {Ctx, ?USCOPE}}; verify_resowner_scope(_, _, _) -> {error, invalid_scope}. verify_scope(Scope, Scope, AppContext) -> {ok, {AppContext, Scope}}; verify_scope(_, _, _) -> {error, invalid_scope}. %% Set up the ETS table for holding access tokens. start() -> ets:new(?ETS_TABLE, [public, named_table, {read_concurrency, true}]). stop() -> ets:delete(?ETS_TABLE). %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.11/LICENSE0000644000232200023220000000203614233730101014747 0ustar debalancedebalanceCopyright (c) 2012-2014 Kivra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.