webmachine and redirect unauthenticated users - erlang

In my new project I want to use webmachine and mochiweb. First thing i want to do is authentication.
I edit "dispatch.conf" and make some resources, like:
{["auth"], my_res_auth, []}.
{["protected"], my_res_protected, []}.
{['*'], my_res_default, []}.
When some one access "protected" resource i want to redirect him to "auth" resource if he not logged in. "auth" resource contains web form with user name and password, it do all auth work.
I put such code inside of my_res_protected.erl:
is_authorized(ReqData, State) ->
case my_auth:is_authorized(ReqData) of
true -> {true, ReqData, State};
false ->
% here should be something to redirect user to "auth" resource
% currently i put such thing, which is incorrect:
{true, wrq:do_redirect(true, wrq:set_resp_header("location", "/auth", ReqData)), State}
% dont know what should i put instead of "true"
end.
i googled some example of how to do it, but dont like that i should have to put this functions in all resources, which requires auth.
Is there any way to do it?

i think i found right way, put this code into auth.hrl file and include it in my resources
is_authorized(ReqData, State) ->
case my_auth:is_authorized(ReqData) of
true -> {true, ReqData, State};
false ->
% there i got address, to which should i redirect
% this address is defined in dispatch.conf
% and walk trough my_res_protected:init/1 into State
case proplists:get_value(do_redirect, State, false) of
false ->
{{halt, 401}, wrq:set_resp_header(
"Content-type", "text/plain",
wrq:set_resp_body("NOT AUTHORIZED", ReqData)
), State};
Location ->
{{halt, 302}, wrq:set_resp_header("Location", Location, ReqData), State}
end
end.

In the case where you're not authorized and do_redirect is false, why not just return { false, ReqData, State } like webmachine expects for is_authorized(), instead of constructing the response yourself?

Related

Parsing ejabberd packet with erlang

I am using ejabberd18.09 for IM application. the application has few features that needed to add extensions to the ejabberd(xmpp) messages.
I created a custom module on the offline_message_hook to capture the offline messages and send them to my own url for further processing .
the messages that are being sent to ejabberd have different cases depending on the type of message as following
if I am sending a photo the message will be as following
<message xmlns="jabber:client" xml:lang="en" to="someuserjid2" from="{someuserjid}" type="chat" id="mP8tO-8">
<mtype xmlns="urn:xmpp:mtype" value="media" />
<url xmlns="urn:xmpp:url" id="myId" mediaType="photo" link="myphotourl.com" />
<body>thumbnail string</body>
</message>
when sending a text
<message xmlns="jabber:client" xml:lang="en" to="someuserjid2" from="{someuserjid}" type="chat" id="mP8tO-8">
<mtype xmlns="urn:xmpp:mtype" value="text" />
<body>Hi John</body>
</message>
when sending a location
<message xmlns="jabber:client" xml:lang="en" to="someuserjid2" from="{someuserjid}" type="chat" id="mP8tO-8">
<mtype xmlns="urn:xmpp:mtype" value="location" />
<location xmlns="urn:xmpp:geo" lat="1.2" lng="2.2 " />
<body>location thumbnailstring</body>
</message>
I used a .erl code to read the body and the message ID as following
create_message(_From, _To, Packet) when (Packet#message.type == chat) and (Packet#message.body /= []) ->
Body = fxml:get_path_s(Packet, [{elem, list_to_binary("body")}, cdata]),
MessageId = fxml:get_tag_attr_s(list_to_binary("id"), Packet),
post_offline_message(_From, _To, Body, MessageId),
ok.
what I want is how (in erlang) can I read the value attribute of the mtype tag the create a switch statement on ( media, location , test ) values so that I can process each message separately ?
You can pass attr in the list of arguments to fxml:get_path_s to pick out the value of an attribute of a certain element:
case fxml:get_path_s(Packet, [{elem, <<"mtype">>}, {attr, <<"value">>}]) of
<<"media">> ->
%% handle media...
<<"location">> ->
%% handle location...
<<"">> ->
%% no mtype element, or missing value attribute!
%% let's handle that case as well
end
Another thought: do you actually need the <mtype> element? It looks like you could just check for the presence of a <location> or <url> element. You could do it like this:
case fxml:get_subtag(Packet, <<"location">>) of
false ->
%% No location. Try url
case fxml:get_subtag(Packet, <<"url">>) of
false ->
%% Neither location nor url
%% Handle this case somehow...
Url ->
%% Do something with Url...
end
Location ->
%% Do something with Location...
end
The code gets a bit messy, since we need to nest the case expressions, unlike the previous version where we just checked the value of a single expression. One thing you could do is writing a helper function that tries each element in turn:
find_one_of(Packet, []) ->
not_found;
find_one_of(Packet, [ElementName | Rest]) ->
case fxml:get_subtag(Packet, ElementName) of
false ->
find_one_of(Packet, Rest);
Element ->
{ElementName, Element}
end.
And then call it like:
case find_one_of(Packet, [<<"location">>, <<"url">>]) of
{<<"location">>, Location} ->
%% Do something with Location...
{<<"url">>, Url} ->
%% Do something with Url...
not_found ->
%% Neither location nor url
end

Delete method - Catch UnauthorizedException from Rails with Angular

I have an Angular method that deletes a user from the users table, but the authorization is happening in Rails. If an unauthorized exception is thrown when deleting the user, Angular doesn't catch the exception, so it appears as if the request was successful.
From my research, this is dealt with using interceptors, but I haven't been able to get it to work. I have the following Angular method and interceptor.
Is this the correct way to do this? If not, what is? If yes, what am I doing wrong? Not really sure how an interceptor is supposed to work, and the docs haven't been very helpful.
service('UserTableService', [ '$http', ($http) ->
constructor: ->
destroyUserRow: (id, error) ->
$http.delete("/users/#{id}").success(() ->
# Do something
).error((data) -> error(data))
]).
factory('deleteUserInterceptor', ['$injector','$q', ($injector, $q) ->
(promise) ->
$http = $injector.get('$http')
promise.then null, (response) ->
if response.status == 401 || response.status == 403
$scope.$emit("Unauthorized")
$q.reject response
]).

How to beautifully make some API functions available only to authorized users in Erlang?

I have functions in my API code, some of which should only return requested results only to those requests that have a correct token. If the request is not authorized, then return a generic 401 response.
I have created a helper function is_authorised() which returns true/false.
Now, in a function that I want to make accessible only to authorized users, I check the result of the is_authorised() function and return respective response.
Here is an example:
get_players(SessionID, _Env, _Input) ->
case is_authorized(_Env) of
true ->
Response = [json_header(), players:select_all()];
false ->
Response = get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
I wonder if it's possible to make this checking look more elegant, like Python decorators used in Flask.
#app.route('/user')
#required_roles('admin', 'user')
def user_page(self):
return "You've got permission to access this page."
I know I can't use custom functions in guards, so I think it's not possible to make it as a guard.
Please advise.
Not as elegant as Python decorators, but you can use higher order functions to make this much cleaner. Create a function that accepts SessionID, Env, Input, and a callback function that should be executed when the user is authorized, and call that from all the functions that need to do the authorization check.
(Untested) code:
authorized(SessionID, Env, Input, Fun) ->
Response = case is_authorized(Env) of
true ->
Fun();
false ->
get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
get_players(SessionID, Env, Input) ->
authorized(SessionID, Env, Input, fun() ->
[json_header(), players:select_all()]
end).
You can pass more arguments to authorized if you want to do more extensive checks. With a role_of/1 function that accepts Env and returns the role as an atom, you can allow access to certain users with something like this:
authorized(SessionID, Env, Input, Roles, Fun) ->
Response = case lists:member(role_of(Env), Roles) of
true ->
Fun();
false ->
get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
get_players(SessionID, Env, Input) ->
authorized(SessionID, Env, Input, [admin, user], fun() ->
[json_header(), players:select_all()]
end).
You might want to look into cowboy or webmachine as they'd give you a framework over a state machine to handle REST requests. Implement the 'forbidden/2' callback to indicate unauthorized access.
On a side note: a 401 status code indicates a failure of authentication at the http layer when doing one of the well known http auth mechanisms like basic. See also https://en.wikipedia.org/wiki/Basic_access_authentication
What you are dealing with is a authorization failure and the correct associated http status code for that case would be 403 (as the forbidden callback from above would return).

User can access internal yaws pages without authentication

I am using the embedded YAWS web-server with yaws cookie session.
I first authenticate the user with user-name & password to allow him the entry to the web pages.
My problem is if the user directly opens the internal web page instead of login page he can view it even without the authentication. How to restrict the user that he must have the cookie to view any internal web page.
In chapter 7 of the Yaws PDF documentation there's an example that does exactly what you're asking about. It uses arg rewriting to redirect unauthenticated requests to a login page.
First we configure an arg rewriter module named myapp in the server portion of yaws.conf:
arg_rewrite_mod = myapp
The myapp:arg_rewrite/1 function checks the incoming request via the #arg{} record to look for a specific cookie, and if not found and the request isn't trying to retrieve one of the three resources returned from the login_pages/0 function, it calls do_rewrite/1 to rewrite the request to deliver a login.yaws page instead:
arg_rewrite(Arg) ->
OurCookieName = "myapp_sid"
case check_cookie(Arg, OurCookieName) of
{error, _} ->
do_rewrite(Arg);
{ok, _Session} ->
%% return Arg untouched
Arg
end.
%% these pages must be shippable without a good cookie
login_pages() ->
["/banner.gif", "/login.yaws", "/post_login.yaws"].
do_rewrite(Arg) ->
Req = Arg#arg.req,
{abs_path, Path} = Req#http_request.path,
case lists:member(Path, login_pages()) of
true ->
Arg;
false ->
Arg#arg{req = Req#http_request{path = {abs_path, "/login.yaws"}},
state = Path}
end.
Please see the Yaws PDF documentation for further details.

Add Extra Property to Cowboy Request

We used Erlang/Cowboy to develop a simple chatting service based on WebSockets. When user connects in, an authentication would be done based on the URL parameter, and it would return user id or none for the connection.
My stupid question is, how to store the user id into the Request data structure and the user id can be get for later-on processes?
If you are using cowboy_rest you can use the handler_state to store your user data after authorization. Something like:
-record(rs_state{user}).
rest_init(Req, Opts) ->
{ok, Req, #rs_state{}}.
is_authorized(Req, State) ->
%% authentication code
{ok, User} = ...
{true, Req, State#rs_state{user=User}}}.

Resources