I find myself too many times in front of a situation when I should create two different resources on an API server, that are initiated by one single action. I would call it cross-controller opertaion: an operation that requires logic to be performed in two different controllers
For example, imagine an applicaiton with a User model and an Account model
User can be associated to multiple accounts.
In my REST API I have an /accounts/ endpoint and a /users/ endpoint which are responsible for the Account and User model respectively, and /accounts/:account_id/users endpoint for the assocation between these 2 models
So if a user choose to disassociate itself from an account I make such a request: HTTP DELETE /accounts/:account_id/users/:id
When a user registers I obiously POST /users/ to create a new user
There is also an option for a user to register and get directly associated to an account (by a special invitaion for example). In such case I would have to register a user and create an association to the account being invited to, preferably doing that in a single request to the server.
I know I can do a request to /users/ in order to create the user and then another request to /accounts/1/users/1 to create the link between the user and the account, but doing so only to stick to the REST principles seems redundant.
Another option is to create a different action such as register_from_invitation that will take care of both action but then the question is where it should sit. In the AccountUsers controller, in the Users controller or in the Accounts controller. This also seems wrong to me.
Any prefered way to solve such a design issue?
I can see several possible solutions.
The method that you described (making to requests: first to /users and second to /accounts/...) is the most REST-full. It is simple, logical and used widely. It may look a little awkward, but it's not. Here you split your logic strictly, not mixing up methods from two controllers.
Then, you can always accept requests to POST /accounts/:account_id/users/. And here create a new user and bind it to the account. From the outer space of your app it looks good, but inside it will cause a slight mix of User and Account methods in Account controller. That's no good.
If you still want to have register_by_invitation method, you can confidently put it in Users controller. Create user there and bind it to some Account you receive in request params.
However, if you want to keep your app RESTfull and do not want to mix the logic from several controllers into one, there's only one I see.
Consider adding another resource called UserAccountMembership. The benefit of this is that you'll separate responsibilities: User and Account resources will remain clean, and at the same time you'll get a resource where you expose specific information about a "connection" between those two (like date/time of a link creation, active/inactive status etc.):
/useraccountmemberships/{useraccountmembershipid} POST, PUT, GET, DELETE
{
"Id": useraccountmembershipid,
"UserId": userid,
"AccountId": accountid,
"Timestamp": "2018-1-1",
...
}
You may still want to allow access to memberships via a parent resource, which is fine:
/users/{userid}/useraccountmemberships GET
/accounts/{accountid}/useraccountmemberships GET
Related
When it comes down to good RESTfull setup, what is the best practice for providing results that pertain to the owner as the requestor and results that pertain to a user wanting data owned by another user.
I have read that a resource should have max 2 base URLs so how to handle say,
Get all items for authenticated user
Get a single item for Authenticated user
Get all items for a particular user
Get a single item for a particular user
Although your question is a bit unclear, it seems to me you might mix up "Resources" as in HTTP resources, and Model objects or database rows.
The two do not necessarily have a 1-to-1 relationship, or even 1-to-2 relationship as you seem to imply. You can expose a database row in multiple "forms" as resources, there is no limitation how many times you can aggregate, transform or publish the same information, as long as those are all semantically different things.
So, back to your problem. You can publish resources pertaining to the authenticated user, and just users independently which might also contain the current user. With an URI structure for example like this:
/currentuser
/user/1
/user/2 <- might be the same as /currentuser
/user/3
...
There also could be a list of users recently logged in:
/recentuser/444
/recentuser/445 <- might be again /currentuser
...
That would be a third reference on the same user, but it is ok, because all of those have a different meaning, might even have different representations to offer (one might offer more information than others).
I've been using /d2l/api/lp/1.4/enrollments/myenrollments/ to get a list of enrollments for the current user. Now, I want to just get the enrollments where the user is in an instructor role. So, I'm trying to use:
/d2l/api/lp/1.4/enrollments/users/{userId}/orgUnits/?roleId=105
When I use that, I get an empty list of Items back, with or without the roleId specified.
My expectation is that just calling it without the roleId would return the same list as /d2l/api/lp/1.4/enrollments/myenrollments/. But, I always get an empty list, except when I log in as a system administrator. Only in that case do I get anything back.
Does anyone have any suggestions on what I might be doing wrong?
The various my* API calls specifically exist to provide end users to fetch back details about the system that they should know, but segregated from information they shouldn't (that's available through the more general routes for a particular area). Enrollments is a good example of this. And end-user should be able to see their own enrollments, but they should not have generalized access to enrollment records. In particular, the D2L system treats the D2L user role belonging to an enrollment as fairly privileged information, and a side effect of this is that it's not generally visible to end users.
One way that applications and services can cope with achieving goals that the end-user cannot themselves perform is to have set up a "service account" that the app can use to make calls of an administrative nature, to fetch back data that they can use in the business logic around presenting information to end users. In this particular case, you could, for example use the service account to make calls about a user's enrollments, and then present the user with logic that could filter the list of their enrollments by "these are the student ones, and these are the ones where you're a teacher, and a tutor, and so forth".
But you'd also need to carefully consider the implications of this type of activity in balance against the intentions of the client LMS's policies and administration. Even this level of information may be giving away too much to end users, in the eyes of a client LMS administrator.
Using a service account to let an app make administrative level calls must always be done with great care around the issue of information/functional leakage to end users.
So I read about how implementing your own authorization routines are bad!
http://www.nashcoding.com/2011/02/05/using-the-forms-authentication-membership-provider-on-appharbor/
And I got scared, because I've been implementing my actions as such (example, preventing access to account details if authenticated user is not the logged in user)
public ActionResult DisplayAccount(int someid){
Account a = context.Accounts.Single(a => a.id == someid);
// currentUserId() returns userid from FormsAuthentication
if (!a.owner == currentUserId()){
/* Not Authorised! */
}
}
Which apparently means it will break if ASP decides to cache my action (so the action doesn't even get executed).
So I'm now looking into using AuthorizeAttribute to do what I need to do, which is
prevent access to an action if not authenticated
check if authenticated user has access to the retrieved resource
However whenever I think about it, I can't think about how to implement the 2nd point. Roles don't work because its on a site wide level, but within the application there users have roles as well (e.g. Owner, Moderator, Contributor, User etc.), and they only have these roles within their respective parts of the application (e.g. owner of thread, contributor to wiki, moderator of forum etc.)
I have run into several examples of overriding AuthorizeCore. I can sort of imagine creating multiple AuthorizeAttribute subclasses for each resource I have (luckily not many), But just by looking at it, does that mean I have to query the database everytime I hit that action to ensure that the logged in user should be able to access that data, then query the database in my action to get the model, instead of doing that in my query?
So my questions are
am I getting too worried about caching too much? Will any of the following happen
website caches user A details, which is rendered on user B's screen?
website caches admin version of a page (with edit controls), and normal user sees cached version?
Using AuthorizeAttribute is a given, but how do I achieve what I need to do in point 2 without having to hit the database prior to the Action? Or what is the best way to achieve it in any case.
Or do I only use AuthorizeAttribute to determine if the user is logged in, and do other checking logic in my action?
Anyway, I hope this post isn't treading on any old paths (I couldn't find anything on this that I found definitive)
Edit: I guess, if I don't enable caching this problem wouldn't occur, is this correct?
Edit: for now, I am going to going to use vanilla AuthorizeAttribute, then check resource level access in my actions, then make sure I don't use caching for any authenticated actions. Hopefully will get more answers for this over the week.
I used the following approach in a recent project, by creating a DataRightsAttribute that used an enumeration for each supported model type. It works by first extracting the id from the route data, formcollection or querystring. Then it queried up the model type determined by the enum, and did the appropriate check to see if the current user is authorized to access it.
Usage was like this:
[DataRights(ModelType.Customer)]
This was used alongside AuthorizeAttribute (which we overrided), and never noticed any problems with caching.
What would be the best way to go about giving users the ability to share a private link that enables anyone who clicks it to view a certain page/document/item that have privacy restrictions in place?
In my case:
A User creates events which are limited to certain groups of relationships in the database (namely: friends, friends of friends, etc.) I have a :before_filter in the event controller that checks the eligibility of the current logged in user to make sure that that user has permission to see the event. If they don't they get booted to the root page with an error message.
However, I want a special scenario to exist where a user can create an event with those same privacy settings and IN ADDITION, be able to share a special link with his or her friends via e-mail, facebook, etc. Those users do NOT need an account (but will need to make one in order to sign up for the event). This is important because there is also a :before_filter in the application_controller which makes sure a user is logged in.
I'm thinking there is something I could do with routing here... Right now I just have the simple /events/72 setup. Should each event have two different links: a normal one, and a "special code" version which enables them to bypass those two :before_filter?
What are people's thoughts?
I agree with David Lyod's answer (separating this concern in a different controller).
But for creating the hash I strongly recommend you salting the hash with some secret phrase.
require "digest"
Digest::SHA512.hexdigest("#{created_at}#{user_id}.mysupersonicsecretSALT")
Doing this it is not possible, without the knowlegde of the secret phrase, to calculate the hashes and test them against your system until it hits an existing one.
If you're handling sensitive data you should not be lazy.
Cheers,
Lukas
I would have a separate controller that uses a hash value to reference the event.
Something simple like the created_at + user_id hashed to create a unique reference.
You could also simply skip the check on a certain action but I would much prefer the first solution .
I have what seems like a common issue with SaaS applications, but have not seen this question on here anywhere.
I am using ASP.NET MVC with Forms Authentication. I have implemented a custom membership provider to handle logic, but have one issue (perhaps the issue is in my mental picture of the system).
As with many SaaS apps, customers create accounts and use the app in a way that looks like they are the only ones present (they only see their items, users, etc.). In reality, there are generic controllers and views presenting data depending on the customer represented in the URL. When calling something like the MembershipProvider.ValidateUser, I have access to the user's customer affiliation in the User object - what I don't have is the context of the request to compare whether it is a data request for the same customer as the user.
As an example,
One company called ABC goes to abc.mysite.com
Another company called XYZ goes to xyz.mysite.com
When an ABC user calls
http://abc.mysite.com/product/edit/12
I have an [Authorize] attribute on the Edit method in the ProductController to make sure he is signed in and has sufficient permission to do so.
If that same ABC user tried to access
http://xyz.mysite.com/product/edit/12
I would not want to validate him in the context of that call. In the ValidateUser of the MembershipProvider, I have the information about the user, but not about the request. I can tell that the user is from ABC, but I cannot tell that the request is for XYZ at that point in the code.
How should I resolve this?
Because Authorize is on the same thread as the request you could determine the subdomain by inspecting:
System.Web.HttpContext.Current.Request.Url.DnsSafeHost
Doing so on each call would certainly keep things in order, however this is a purely cosmetic check during authorization. I recommend that you simply look at this information during authentication. Once you know they are requesting XYZ and authenticate them into it, authorization should be only concerned about controlling features/data they have access to as XYZ. Their being from XYZ should be stored as part of the CurrentUser from that point.