I develop and maintain a customer portal, written in Perl/Catalyst. We make use of the Catalyst authentication plugins (w/ an LDAP storage backend, coupled with a few deny_unless rules to ensure the right people have the right group membership).
It's often that in managing a customer's permissions, we have the need to test out a user's settings before we hand things over. Currently, our only recourse is to reset a user's password and log in ourselves, but this is less than ideal, particularly if the user has already set their own passwords, etc.
My question is this: for Catalyst, has anyone come across a method of impersonating a user account such that, given the correct super-admin privileges, one could impersonate another account temporarily while testing out a setting, and then back out once done?
If not in Catalyst, then how have people approached this in other frameworks, or their own custom solutions? Admittedly, this is something that introduces a potentially egregious attack vector for a web application, but if forced to implement, how have people approached design for this? Perhaps some serious cookie-session-fu? Or possibly an actualID/effectiveID system?
We use a custom authenticator controller, a custom user class (MyApp::Core::User) and several realms:
package MyApp::Controller::Auth;
...
sub surrogate : Local {
my ( $self, $c ) = #_;
my $p = $c->req->params;
my $actual_user = $c->user; # save it for later
try {
$c->authenticate({ id=>$p->{surrogate_id} }, 'none');
$c->session->{user} = new MyApp::Core::User(
active_user => $actual_user,
effective_user => $c->user );
$c->stash->{json} = { success => \1, msg => "Login Ok" };
} catch {
$c->stash->{json} = { success => \0, msg => "Invalid User" };
};
$c->forward('View::JSON');
}
In myapp.conf I use something like this:
<authentication>
default_realm ldap
<realms>
<ldap>
# ldap realm config stuff here
</local>
<none>
<credential>
class Password
password_field password
password_type none
</credential>
<store>
class Null
</store>
</none>
</realms>
</authentication>
That way we're creating a normal Catalyst user object, but wrapping it around our custom user class for more control. I probably could have created an specialized realm for surrogating, but I've chosen using my own user class instead. It was done a while back and I can recall why we did it that way.
Related
Context
I've build a RESTful API server in Actix-Web with Rust that's hosted on a Heroku paid plan. It has n amount of publicly available endpoints to access content, alongside 3 strictly admin-only endpoints (for creating, editing, and deleting public content).
I am the only developer who'd ever need to access the admin-only endpoints - and infrequently at that. Several random users will be using the publicly available endpoints daily.
Normally, I'd implement an authentication/authorization strategy akin to this using JWTs (but obviously in Rust for my case). However, the added complexity that comes with this "more common" solution seems overkill for my simple use-case.
My theorized solution
Could I add a username and password field to the .env file in my project like so in order to match against a username and password passed in the admin-only handler functions?
... OTHER KEYS ...
USERNAME = my_really_long_random_username
PASSWORD = my_really_long_random_password
At first glance I'm storing passwords in plain text... but, there's only 1 and it's in my .env file, which is private by default.
All I'd do for the admin-only routes then is this (pseudo-code):
pub fn router_handler(passed_data) -> HttpResponse {
if passed_data.username == env.username && passed_data.password == env.password {
// CONSIDER THEM ADMIN
} else {
// BLOCK THEM AS THEY'RE NOT AUTHENTICATED
}
}
What I've tried
I have yet to try this strategy, but I'm curious about your opinions on it.
Question
Is my theorized solution secure? Does it seem reasonable given my use-case?
Response to question: jthulhu - is this what I do?
So, my .env file should look something like this:
... OTHER KEYS ...
USERNAME = a98ysnrn938qwyanr9c8yQden
PASSWORD = aosdf83h282huciquhr8291h91
where both of those hashes are the results of running my pre-determined username and password through my to_hash function which I added below (likely using a lib like this).
Then, my handler should be like this (psuedo-code):
pub fn router_handler(passed_data) -> HttpResponse {
if to_hash(passed_data.username) == env.username && to_hash(passed_data.password) == env.password {
// CONSIDER THEM ADMIN
} else {
// BLOCK THEM AS THEY'RE NOT AUTHENTICATED
}
}
You should never store passwords in plain text in a server, because if someones breaks in your server, and can read that file, they now have access to everything (whereas they might previously not). Not only that, but most people tend to reuse passwords, so storing one password in plain text means exposing several services where that password is used.
Instead, you should hash the passwords and store the hash. To perform a login, check if the hash of the given password corresponds to the one stored. This mechanism can be used with files or with databases alike, and is pretty much independent on how you actually store the hashes.
I want to provide a me endpoint because not everyone should have access to all resources. The current logged in user should only have access to his own resources. So let's assume you would have a simple Todo REST API with an endpoint returning all tasks from a single user
GET /users/{username}/tasks
The current logged in user should not be able to get information about other users. A possible solution for this would be
GET /users/me/tasks
This has already been discussed here
Designing URI for current logged in user in REST applications
The problem is that I also want to keep the endpoint from above for development purposes (a private/hidden endpoint). Unfortunately both endpoints match the same route. So me could be the username.
I don't want to prevent a username called me with an if-statement like
if(username == "me")
throw new ConflictException("Username 'me' is forbidden");
I think this would be bad design. A possible solution would be to avoid embedding me in a resource and instead embed the resources in me endpoints. So instead of
GET /users/me/tasks
GET /users/me/orders
POST /users/me/tasks
PATCH /users/me/username
DELETE /users/me/tasks/{taskName}
I could remove the users resource and make me the initial base resource. As you might guess /users/:username extracts the username from the url parameter and /me extracts the username from the json web token but both endpoints run the same logic.
So when I want to keep private/hidden endpoints to fetch a user by username do I have to make me a separate resource? Or is there a way to keep me only as a replacement for the username parameter?
EDIT
I tried to create a simple route example. As you can see most of the endpoints of users and me are mirrored and only differ by the user identification (username as parameter or jwt payload). And for this sample only the me endpoints are accessible to everyone. Other endpoints are private / hidden.
users
GET / => get users => private
GET /:username => get user => private
GET /:username/tasks => get tasks from user => private
GET /:username/tasks/:taskName => get task from user => private
POST /:username/tasks => create user task => private
PATCH /:username/username => update username => private
DELETE /:username => delete user => private
DELETE /:username/tasks/:taskName => delete user task => private
tasks
GET / => get tasks => private
me
GET / => get current logged in user => public
GET /tasks => get tasks from current logged in user => public
GET /tasks/:taskName => get task from current logged in user => public
POST /tasks => create task for current logged in user => public
PATCH /username => update username => public
DELETE / => delete current logged in user => public
DELETE /tasks/:taskName => delete task from current logged in user => public
I think you are getting confused because your definitions are a bit tangled.
Here's the good news: REST doesn't care about the spelling conventions you use for your identifiers. So if it makes your life easy to have
GET /users/12345/tasks
GET /me/tasks
Then you can implement both of those, make sure that the access restrictions are correct, and off you go.
There's nothing wrong, from a REST perspective, about using
GET /users/12345/tasks
GET /users/me/tasks
The spellings of the target-uri are different, which means that from the perspective of a general-purpose client they identify different resources.
BUT... the routing implementation that you are using to implement your API may not allow you to easily distinguish these two cases. What you have effectively got here are two different matches for the same pattern, which requires some extra clever to remove the ambiguity; if your routing library/framework doesn't offer that, then you are going to have to shim it in yourself.
And, as you note, you've got a huge mess if the token "me" itself could match a real identifier.
REST doesn't have a concept of "base resource" - at least, not one that lines up with what you are thinking about. There's no inherent relationship between /users and /users/12345 from the REST perspective. Many server frameworks do treat request handling as a hierarchy of "resources", but again: that's an implementation detail of your particular server.
REST really only cares about your interface - that you understand HTTP requests in the standard way.
Which all brings us back to the good news; since REST doesn't care what spelling conventions you use, you are welcome to make any arbitrary choices you like... including whatever spelling convention makes your internal routing framework easy to work with.
So if your routing framework is telling you to create a different "initial base resource" to support your family of "me" endpoints, then just do that.
I have two applications on the same domain, but they are both creating a plack_session every time the user logs in. It happens because application A overwrites application B's plack session.
It's a complex process to remove one of them and make them use one that is created by a central application, but for now, how can I change one of those 'plack_session' names to something like 'plack_session2' so they don't see each other?
I don't even know if it is possible.
Here is the document for Plack Session, but I can't see anything that can help me here.
As shown in the documentation you link to, the Plack session middleware is enabled with code like this:
builder {
enable 'Session',
state => Plack::Session::State->new;
$app;
};
Later in the same document, you'll find the documentation for the new() method:
new ( %params )
The %params can include session_key, sid_generator and sid_checker however in both cases a default will be provided for you.
session_key
This is the name of the session key, it defaults to 'plack_session'.
...
Putting all this together, I'd guess (and I haven't ever done this) that you can do what you want with code like this:
builder {
enable 'Session',
state => Plack::Session::State->new(
session_key => 'my_session_key',
);
$app;
};
I want to test hiding and unhiding of an entry. I conduct the following tests in Mojolicious t/basic.t:
my $t = Test::Mojo->new('AdminApi');
$t->get_ok('/publications/hide/1');
$t->get_ok('/read/publications/meta')->content_unlike(qr/Paper with id 1:/i);
$t->get_ok('/read/publications/meta/1')->content_like(qr/Cannot find entry id: 1/i);
$t->get_ok('/publications/unhide/1');
$t->get_ok('/read/publications/meta')->content_like(qr/Paper with id 1: <a href/i);
$t->get_ok('/read/publications/meta/1')->content_unlike(qr/Cannot find entry id: 1/i);
My problem is that the two lines '/publications/hide/1' and '/publications/unhide/1' do not hide and unhide the entry. The state of the entry remains untouched.
If I repeat the steps manually in the browser everything works well. For the obvious reasons I want to have it automated with the tests. How to do this?
EDIT: The calls '/publications/hide/1' and '/publications/unhide/1' change the state of the database - just a simple UPDATE query. The change applies to the whole application - for all users. But one needs to be logged in as a user to hide/unhide. Question: how do I emulate a logged user during the test?
Contents generated by '/read/publications/meta' and '/read/publications/meta/1' can be read without login.
Bitbucket Repo
File with test code: basic.t
As you have already said, you need to be logged in to perform the hide and unhide action.
my $t = Test::Mojo->new('AdminApi');
You are creating a new UserAgent here. The Test::Mojo class inherits from Mojo::UserAgent. It has a cookie_jar and thus keeps a session alive. You need that to perform this action:
$t->get_ok('/publications/hide/1');
But right now you are not logged in. What you need to do is log in the user. Looking at the code in your repository, you actually assert that you are not logged in.
$t->get_ok('/')->status_is(200)->content_like(qr/Please login or register/i);
Before you perform the hide, you need to log in the user. After digging a bit in your code I found the action and the template to do that, so I know what the request needs to look like.
$t->post_ok(
'/do_login' => { Accept => '*/*' },
form => { user => 'admin', pass => 'hunter2' }
);
Now your $t UserAgent should be logged in and you can do the hide. Note that get_ok only checks if there was no transport error. So in fact it would make sense to now check if in fact you are now logged in.
You could do that by introspecting the session in the application, by checking the logfile (you are writing "Login success" there) or by checking the page for some string that says that the user is logged in. In templates/display/start.html.ep there is a text that welcomes the logged-in user, so you can use that.
$t->post_ok(
'/do_login' => { Accept => '*/*' },
form => { user => 'admin', pass => 'hunter2' }
)->text_like(qr/Nice to see you here admin/i);
Because text_like uses the text-nodes, the <em> around the username is not relevant in the test.
Right, now we know you are logged in. Time to switch the thing on and off.
$t->get_ok('/publications/hide/1');
Because there is no obvious error thrown for that as far as I can tell, I don't see how to test the success of that. Status code is one way, but there might be something in the content as well that you could test.
To verify the state of the application, you would now call the publication.
$t->get_ok('/read/publications/meta')->content_unlike(qr/Paper with id 1:/i);
$t->get_ok('/read/publications/meta/1')->content_like(qr/Cannot find entry id: 1/i);
Right. But remember, our $t is still logged in. Maybe the logged-in user is allowed to see hidden stuff as well as unhidden stuff. Maybe they are not.
It's probably safer to make a second UserAgent that's not logged in, and check with that one as well.
# check with an unauthorized user
my $t_not_logged_in = Test::Mojo->new('AdminApi');
$t_not_logged_in
->get_ok('/read/publications/meta')
->content_unlike(qr/Paper with id 1:/i);
$t_not_logged_in
->get_ok('/read/publications/meta/1')
->content_like(qr/Cannot find entry id: 1/i);
Now basically you repeat the same thing by unhiding your content and testing again. Rinse and repeat.
Keep in mind that unless you are using an explicit testing database (which you seem not to do), you cannot be sure that there even is an entry 1. Or what the name of that is. You should use fixtures for the tests. You could, for example, create a fresh instance of the DB using sqlite and work with that.
I've user roles: user, manager, admin. I need to authenticate them in controllers (methods). For example only admin can delete (now it looks like this, need to change that only admin should have permission):
def deleteBook(id: Int) = DBAction {
findById(id) match {
case Some(entity) => {
books.filter(_.id === id).delete
Ok("")
}
case None => Ok("")
}
}
I've many controllers and methods. I need to authenticate before process request (for example deleting book). My routes file contains:
...
DELETE /books/:id #controllers.Book.deleteBook(id: Int)
...
Some routes are only accessible to admin and manager. Some are for all types of users.
I'm currently seeing deadbolt2scala authorization module for play.
Can you recommend best way to authenticate multirole users in playframework scala?
I've managed to do this by using StackableControllers provided by https://github.com/t2v/stackable-controller
Basically, I use a basic access control list provided by my application.conf. I start by checking if there is a user in my request. If there is one, I can check if he has sufficient access rights to perform the action.
Such a feature may be implemented using BodyParser composition too. I've never done that, though, so someone else's advice may be better for you.