I'm currently setting up a new http4s server and looking into the authentication/authorisation mechanism and found TSec.
After going through TSec's documentation and also this example code, I couldn't figure out a way to implement authorisation on a per resource basis.
Say, for example, I can do the following to authorise an admin user (taken from example above):
TSecAuthService.withAuthorization(AdminRequired) {
case request # GET -> Root / "account" asAuthed user =>
Ok()
}
But how do I authorise admin user of a particular account based on the path:
GET -> Root / "account" / AccountId(accid)
such that only admin user from the particular account (i.e., accid) is authorised?
I had a look into the few instances documented here but they don't seem to fit my requirement.
Related
In my project I have lot of endpoint views (APIViews, ViewSets). For all of them now I set permissions, some of them are default (e.g. AllowAny) and some are custom created:
permission_classes = (IsUserHaveSomePermission,)
Now I want to implement some flexible system, that will allow me to specify set of allowed endpoints for each user, for example:
On front-end I want to select some user and have a list of checkboxes that correspond to project's endpoints.
This is just an utopian solution, some details may be changed, but the main question is to how make something similar so that admins can basically dynamically change list of allowed endpoints/views for user?
thanks in advance
This solution can be implemented by storing if the user has permission to access the current request method and request path.
Create a new db model for storing the user, request method and request path. Lets say the name of the model is RequestPermission
Instead of the path you can store a constant representing the url so that you have the flexibility of editing the path later on. This constant can be the url name which is supported by django.
class RequestPermission(models.Model):
user = user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='request_permissions')
method = models.CharField(max_length=10)
path_name = models.CharField(max_length=200)
create a custom permission class:
class IsUserResuestAllowed(permissions.BasePermission):
def has_permission(self, request, view):
user = request.user
# you can choose how to get the path_name from the path
path_name = get_path_name(request.path)
return RequestPermission.objects.filter(user=user, method=request.method, path_name=path_name).exists()
Now you can use this class as the default permission class in rest framework settings or use it per view.
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.
Is it possible to store all records of the news extension (ext:news) on the same storage page, but show only records, which are created by the loggedin backend user?
So the current backend user can just see and edit his own records? Admins should see all records of course.
No, this is not possible, since backend user permissions on record level are not implemented in TYPO3.
So you either have to separate the news records of the users in separate sysfolders or you could try to use hooks (e.g. $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['getTable']) or XClass to customize TYPO3 backend to your needs. I do not recommend the latter, since the TYPO3 backend permission system is complex and you would need to make sure to restrict record access in several parts of TYPO3 (e.g. recordlist, element browser, related news field, ...)
There are two ways to archive that:
If the backend user is not too much. You can just create a page(type
is folder) named with the backend user name. And in the backend user
module you can set the permission(Not for the group user but for the
single backend user only).
if the backend user is too much. and You just wanna set permissions for the group and all backend users are sharing the same rules. You can refer to Hook:https://docs.typo3.org/p/georgringer/news/main/en-us/Tutorials/ExtendNews/Hooks/Index.html so that the basic logic is like this:
2.1 get current logged-in user group.
2.2 if the group is Reporter, we can use the hook for the listing page:
$constraints[] = $query->equals('cruser_id', $be_id);
Edit(3/3/2022):
Hi Chris, Yes you are right.
Today, I have got a chance to dig into the news extension. I think we can still make it by the hook
in your ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::class]['modifyQuery'][$_EXTKEY]
= \T3docs\SitePackage\Hooks\DatabaseRecordListHook::class;
(Please make sure the namespace is correct)
within the file : T3docs\SitePackage\Hooks\DatabaseRecordListHook.Create a function named modifyQuery:
public function modifyQuery($parameters,
$table,
$pageId,
$additionalConstraints,
$fields,
$queryBuilder)
{
if ($table === 'tx_news_domain_model_news') {
$tsconfig = $GLOBALS['BE_USER']->getTSConfig();
if (!empty($tsconfig['options.']['be_users'])) {
$be_users = $tsconfig['options.']['be_users'];
$queryBuilder->andWhere('cruser_id IN (' . $be_users . ')');
}
}
return $queryBuilder;
}
in the user Options tab set Tsconfg : options.be_users = 3,100
(3,100 is the be_user id. And the 2 two backend users's news will show up)
thus, it works for me.
I am developing an api.ai bot that will search for the Vendor name in the database.
a ) if vendor exist -> provide username -> provide password
b) if vendor doesn't exist -> (add vendor -> yes ) or (add vendor -> No)
I have a webhook which is checking the vendor exist in database or not .
Bot Scenario: (Example )
Case1:
User: Do Alpha exist as a vendor?
Bot: yes, Alpha exist in Database. Please Provide User Name.
User: abc#gmail.com
Bot: Please Provide Password?
User: abcdef
Bot : Welcome
Case 2:
User: Do Beta exist as a vendor ?
Bot: No Beta is not a vendor. Do you want to Register?
Case 1:
User: Yes
Bot: Please fill this Form.
Case 2:
User: No
Bot: Is there any other way I can help
One thing I have figured out, I have to use output context to trigger the intent. But how can I do it in this complex case? and how can I call multiple to follow up intent using Output Context?
I might be using a bad approach, Is there any other way to solve this ?
I do have a follow-up question.
when we pass the fulfillment response back to dialogue flow. The response print on bot console will be the default text response, how can I get "fulfillmentText" to be the Response.
Thank you Guys. This is the followup Intent scenario.
This is not complex, you are doing it wrong by having two intents for collecting username/password.
Try the following way
When you detect that your vendor is present - set the context in webhook, as say, "vendor-present"
When the vendor is not present - set the context in webhook, as say, "vendor-new"
Use lifespan (the number at the left side of the context) to set the lifetime or validity of the context.
Create a separate intent for existing vendor - say "Vendor Data Collection" for collecting username and password. Set input context as "vendor-present" in the Dialogflow. Here you will collect these as parameters in the same intent (see image below). Mark these parameters as 'required' so that they must be collected by your bot. Use the Prompt section to put your response question for collecting information like "Please provide username".
If the vendor is not present, use existing intents and set input context as "vendor-new" in the Dialogflow.
Now, few things to note - the username parameter can be collected using the system entity #sys.given-name. But it is not very accurate with the Non-American/English names. I am not sure if this is improved or not. Secondly, there is no system entity to collect passwords, so you need to set the entity as #sys.any and in the webhook, you need to use regex to extract passwords on your own. BTW - you are not supposed to share passwords!
Hope this helped you!
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.