Hi I’m learning Blazor and I’m creating a backend & frontend app ( Blazor web assembly)
It works well, but my question is related to security.
I have user, business customer, and invoice. It's a very simple app
And user Id is a GUID and the others (business customer and invoice) are int & sequential (increment by 1 like 1,2, 3 and …)
Each user can not see the other user’s data and must not has access to them
this app has 2 parts, an API backend, and a Blazor frontend.
lets add some data like :
And End users can use the API only too without using frontend
I have in Invoice Repository:
Task<Invoice> GetInvoicesListAsync(int businessId);
and after verification users can access its invoices List
if we have a verified enduser like “Alex” (in the second row pf user table), can be retrieve the data that its not belong to him
Because BusinessId is sequential and when he knows his business Id is 99 , he can retrieve invoices for business 100,101,102 and … just with some trying
like :
var lst = await GetInvoicesListAsync(101);
my questions are:
1- How we can stop that? Do I need to enforce enduser to enter UserId as well for security into the API params like this :
Task<Invoice> GetInvoicesListAsync(Guid userId, int businessId);
2- If I add userId then is that has impact on the performance? because in this case I need to do Inner join with user table too and if we have complex database then it has big impact on the performance
3- what is your advice
thanks
Answers as point list:
No, you don't. The method GetInvoicesListAsync(int businessId) is good enough from a security point of view. You need to extract the list of business per user and check if the requested business is in the list, or more simply add a where / join condition filtering the invoices on the businesses of the user.
inner join is not a problem if you have the right indexes. Use your DB explain query plan to verify that your query is using index and do not execute any full table scan
when your user call the API you need to verify the user (valid token or cookie) and to obtain teh list of the business associated, so you don't need to receive this info from the API parameters. This is the biggest concern from a security point of view. The rule for secure API is "hey API, what's my name?"
Related
To give some context on what I know. I learn some time ago from this tutorial even when you set a field from your model READ ONLY on the View how easy is use tools like fiddler to alter the POST data and change the value.
What you do in this case is use Model Binding to specify the list of fields you want Edit and ignore the rest
[HttpPost]
[ActionName("Edit")]
public ActionResult Edit_Post(int id)
{
EmployeeBusinessLayer employeeBusinessLayer = new EmployeeBusinessLayer();
Employee employee = employeeBusinessLayer.Employees.Single(x => x.ID == id);
UpdateModel(employee, new string[] { "ID", "Gender", "City", "DateOfBirth" });
^^^ Name isnt updated
if (ModelState.IsValid)
{
employeeBusinessLayer.SaveEmployee(employee);
return RedirectToAction("Index");
}
return View(employee);
}
Now I learn about Authentication using AspNet.Identity.
Can check if user isn't authenticated to send him to the Login page.
If user belong to certain Role, I can show personalized menus.
Or check the list of contacts for the user and show only those.
But even if you filter the list of contacts based on the user_id before create the view you can access the actions EDIT and DELETE using links like this.
http://MVCDemo/Employee/Edit/1
http://MVCDemo/Employee/Delete/1
And as long you are authenticated, you can change other users data.
So, how you integrate authentication with CRUD actions to avoid unintended actions?
Seem to me to solve this situation I have to apply something similar to the Model Binding example. When get and Edit or Delete post request, first get the Employee from the DB using the id and compare if belong to the current user.
But that create a lot of repeats code across all the functions.
So what is the right way to handle this?
EDIT
To try to make it clear. Lets assume I have an address book application. Each user have contacts:
contact_id user_id name phone ....
1 1 A
2 1 B
3 1 C
4 2 D
5 2 E
6 2 F
The action to show contacts use authenticated user_id to filter the contacts before send it to the view.
Then you have an Edit/Delete actions where you get contact_id and process to Update/Delete. Even when both users have permission to these actions they shouldn't be able to affect other users contacts. But as I explain is very easy change the page using a tool like fiddler.
I can only speak from personal experience but I don't know any MVC features for managing this for you; I've only ever taken the approach you outline of retrieving the data model from the database and explicitly writing the logic to check if the current user has permissions to modify this resource, returning a 401 error if not.
You can compare the incoming request userid with the userid you have in the session, encrypted cookie or web tokens like jwt which ever you use.
Usually if i have load balancer i use encrypted cookie where i store the logged in user's id and compare it with the id i am getting from the request. if both match, update/delete profile else return Unauthorized.
Same can be done in case of Session or tokens as well.
I am using the eBay Trading API with C# .NET SDK
I created a ReturnPolicyType
ReturnPolicyType policy=new ReturnPolicyType();
I set the policy properties and everything seems to work except the restocking fee
policy.RestockingFeeValue = "Percent_15";
And:
policy.RestockingFeeValueOption = "Percent_15";
I've also tried "15%" instead of "Percent_15"
but neither of them show the restocking fee on the listing
I've also asked the question on eBay's developer forums but they are pretty vacant of activity.
My full return policy code is below
ReturnPolicyType policy=new ReturnPolicyType();
policy.Refund="MoneyBack";
policy.ReturnsWithinOption="Days_30";
policy.ShippingCostPaidBy = "Buyer";
policy.RestockingFeeValue = "15%";
policy.RestockingFeeValueOption = "Percent_15";
policy.Description = "Returns are welcome on all items other than those sold on an 'AS - IS' basis. Buyers returning items shipped outside of the US will be responsible for all customs fees as well. Please read and fully understand the terms of our policy in advance if you wish to request a return.";
policy.ReturnsAcceptedOption="ReturnsAccepted";
policy.ShippingCostPaidByOption="Buyer";
The rest of the return policy displays as expected on the listing
To obtain the list of currently supported values, call GeteBayDetails with DetailName set to ReturnPolicyDetails. Then, look for the list of restocking fee percentage values in the ReturnPolicyDetails.RestockingFeeValue containers in the response.
https://developer.ebay.com/devzone/xml/docs/reference/ebay/types/ReturnPolicyType.html
I fetched an item listed using our old listing method and looked through the API call log to see the XML format of the existing listing.
I noticed a tag SellerReturnProfile inside of a tag SellerProfiles
I was able to populate the tags in the additem call like so
item.SellerProfiles = new SellerProfilesType();
var returnpolicy = new SellerReturnProfileType();
returnpolicy.ReturnProfileID = 63410125011;
returnpolicy.ReturnProfileName = "Returns Accepted,Buyer,30 Days,Money Default";
item.SellerProfiles.SellerReturnProfile = returnpolicy;
I had to list shipping profiles and payment profiles in the same way. It seems like if you list one seller profile the other 2 become required. In this case, the return profile was already defined in eBay as our default return profile.
They can be found in Account Settings -> Business Policies, but the id number has to be found with a getitem call on an existing item with the profile set.
It seems like the other call method ReturnPolicyType() might be depreciated as per these two sources
Business Policies Opt-In out soon to be enforced
Mapping Business Policies Management API Fields to Trading API Fields
Any seller who is opted into Business Policies will not be able to use the legacy fields at all, for Payment, Returns or Shipping in any new listing. If legacy fields are passed into the request, they will be ignored and dropped and the seller may get a warning message to that effect.
and
If you pass in Business Policies profile IDs and the legacy fields, the legacy fields will be ignored and dropped.
I'm not even sure how to search for this question, so forgive me if I'm asking a duplicate question and would be grateful for any redirection needed.
I have data (Account Number, Password, Internal Y/N) that is being submitted to an Account Table from Navision. I want to use this data to automatically create a user in the UserProfile table (Username = Account Number, Password = Password) and assign that user to the Admin role if Internal = Y and DealerAdmin if Internal = N.
The data will continue to be maintained for Account numbers in Navision, but the DealerAdmin can add additional users from the website. Is this possible? If so, please provide any pointers to tutorials as to where to start? I presume it's a simple SQL statement. Where do I add this code in MVC so that it gets updated every time there's new data in the Account Table?
If you are using SQL why not use a trigger to create a new record in your User UserProfile when your conditions are met?
If this does not work for you can take a look at the post below and call your proc to move the data over if needed.
ASP.NET MVC 4 intercept all incoming requests
We were working on a project that would pass our e-commerce orders into Acumatica through Web Service API.
As part of the order process, we need to search through Customers to get the one we need, and then we want to have ability to make change to this customer data based on primary key, i.e. BAccountID; however, when I used Web Service API "AR303000Export" to get customer info, I didn't see "BAccountID" in the data that I was getting from Acumatica, therefore I couldn't update that Business Account record based on primary keys (BAccountID and CompanyID, we already have CompanyID).
Is there anyway to get primary key values when doing search through Web Service API?
I noticed I might be able to use "AcctCD", which is called "Customer ID" to update, however, I'm not sure whether that Customer ID is unique or not in database, since it is not specified as Primary key...
Any help would be really appreciated.
Thanks.
Your specific question is how to get BAccountID, and you can - so I'll answer that question. But you probably do want to work with AcctCD.
This snippet of code builds the command list for AR303000Export
var commandgroup = new Command[]
{
AR303000Schema.CustomerSummary.ServiceCommands.EveryCustomerID
, WSTools.NewA4Field(AR303000Schema.CustomerSummary.CustomerID.ObjectName, "DefContactID")
, AR303000Schema.CustomerSummary.CustomerID
, AR303000Schema.CustomerSummary.CustomerName
, WSTools.NewA4Field(AR303000Schema.CustomerSummary.CustomerID.ObjectName, "BAccountID")
, WSTools.NewA4Field(AR303000Schema.CustomerSummary.CustomerID.ObjectName, "Type")
,AR303000Schema.CustomerSummary.Status
, AR303000Schema.GeneralInfoMainContact.Phone1
, AR303000Schema.GeneralInfoMainContact.Phone2
, WSTools.NewA4Field(AR303000Schema.GeneralInfoMainContact.Phone1.ObjectName, "Phone1Type")
, WSTools.NewA4Field(AR303000Schema.GeneralInfoMainContact.Phone1.ObjectName, "Phone2Type")
WSTools.NewA4Field(AR303000Schema.CustomerSummary.CustomerID.ObjectName, "LastModifiedDateTime")
};
You'll notice that some interesting things are just not exposed in the strongly typed schema, but still available if you know how to build the command to reference the field. (Such as the type of phone number in Phone1 being in Phone1Type).
In your case, you need a command to export BAccountID from the same internal object name as CustomerID:
WSTools.NewA4Field(AR303000Schema.CustomerSummary.CustomerID.ObjectName, "BAccountID")
in your export command list. (it is 5th in the snippet above).
There are many ways to build the "Field" object - I have a utility method for that:
public static AcumaticaWS.Field NewA4Field(string objName, string fldName)
{
AcumaticaWS.Field nv = new AcumaticaWS.Field();
nv.ObjectName = objName;
nv.FieldName = fldName.TrimEnd();
return nv;
}
Hope this helps!
I'm creating a maker-checker functionality where, maker creates a record and it is reviewed by the checker. The record creation will not happen until the checker approves it. If the checker rejects it then it should be rolled back.
The idea which i know is create a temporary table of records which will hold the records to be created until the checker approves it. But this method will have 2X number of tables to be created to cover all functionalities.
What are the best practices to achieve this?
Is it something which can be done using Windows Work Flow.? (WWF)
Just add a few more columns to your db.
Status : Current status of record ( Waiting for approval, Approved,
Declined . etc)
Maker : Username or id
Checker : Username or id
Checker Approval/Rejection Time
Alternatively if you have lots of tables like this needs to maker/checker workflow you can add another table and reference all other record to this table.
Windows workflow foundation also could work for you but i persnoally find it difficult to use
If you want revisions for the recored you also need more columns. Such as Revision Number and IsLastRevision.
I dont know what you are using for accessing db and modifiying records. If you are using OR/M you might override Update and do revisions on all saves . For Example
void Update(Entity e )
{
Entity n = new Entity();
// Create a copy of e ( With AutoMapper for example or manually )
e.Current = true;
e.RevisionNumber += 1;
Update(e);
Insert(n);
}
In this case you will have two options:
Create two identical tables and use one table for approved data and one for requested data.
OR
You can create two rows for a user with TypeID(requested/approved). in this case user will create a request with typeID = requested when approver approved that request you simply override current approved record with new one otherwise simply mark requested row with rejected status.