Odoo 14.0 cannot make public route - request

How can I create a public route to access stock data without login authentication?
#http.route('/nuanju/stock_report', auth='public', website=True, methods=['GET'])
For this request, I'm getting this error :
odoo.exceptions.AccessError: You are not allowed to access 'Quants' (stock.quant) records.
This operation is allowed for the following groups:
- Inventory/Administrator
- Inventory/User
- User types/Internal User
Contact your administrator to request access if necessary.
I have also one public user in database

It's resolved by accessing data with sudo
request.env['stock.quant'].sudo().search([])
instead of
request.env['stock.quant'].search([])

Related

Exception in Site.createExternalUser in Apex RESTclass: Site.ExternalUserCreateException: [That operation is only allowed from within an active site.]

I have a Non-Salesforce Auth System which holds usernames and passwords for a few thousand users. I am willing to migrate these users to Salesforce and give access to these users to my Experience Cloud site. I am developing an apex REST Resource which will take username and password as arguments and create a user with that username and password with a community profile. I am planning to call this API from my Non-Salesforce system and migrate all these users. I am using Site.createExternalUser method in this API. I am getting the exception
Site.ExternalUserCreateException: [That operation is only allowed from within an active site.]
The reason I am using Site.createExternalUser is because I don't want to send the welcome email/reset password email to my users since they already have signed up successfully long ago.
I am open to any alternatives for achiving this.
Below is my code:
#RestResource(urlMapping='/createUser/*')
global with sharing class createUserRestResource {
#HttpPost
global static String doPost(){
Contact con=new Contact();
con.Firstname="First";
con.LastName= "Last";
con.Email="first.last#example.com";
con.AccountId='/Add an account Id here./';
insert con;
usr.Username= "usernameFromRequest#example.com";
usr.Alias= "alias123";
usr.Email= "first.last#example.com";
usr.FirstName= "First";
usr.IsActive= true;
usr.LastName= "Last";
usr.ProfileId='/Community User Profile Id/';
usr.EmailEncodingKey= 'ISO-8859-1';
usr.TimeZoneSidKey= 'America/Los_Angeles';
usr.LocaleSidKey= 'en_US';
usr.LanguageLocaleKey= 'en_US';
usr.ContactId = con.Id;
String userId = Site.createExternalUser(usr, con.AccountId, 'Password#1234', false);
return userId;
}
}
You can suppress sending emails out in whole org (Setup -> Deliverability) or in the Community config there will be way to not send welcome emails (your community -> Workspaces -> Administration -> Emails).
Without running on actual Site I don't think you can pull it off in one go. In theory it's simple, insert contact, then insert user. In practice depends which fields you set on the user. If it's Partner community you might be setting UserRoleId too and that's forbidden. See MIXED DML error. In Customer community you might be safe... until you decide to assign them some permission sets too.
You might need 2 separate endpoints, 1 to create contact, 1 to make user out of it. Or save the contact and then offload user creation to #future/Queueable/something else like that.

Implement one general Authorization Service which should be called when I put Authorize attribute on it in multiple applications/APIs

Has anyone an idear what to use as a general Authorization Service and have an working code example or good implementation steps how to implement such of thing.
It takes a lot of time to look what I am after, but didn't found any satisfied solution yet.
IdentityServer is not an option, while my permissions can not be stored as claims, because of the size of the token. It comes with about 200 persmissions, so it should be done in a dbcontext or something.
I looked at the PolicyServer, but it wasn't working as I expected. When I installed it at the IS4 application, it works on the IS4 controllers, but when the Authorize is called from an external application, it doesn't call the Authorize override at all were it should check the permissions.
And it seems that the permissions aren't set in the external application either in the User.Claims or what so ever. I'm missing some settings I think.
What I want to accomplish is that I have one permissions store (table) (which for example contains a bunch of index, add, edit or delete button or what so ever). The should be given to the autheniticated user which is logged in. But this single persmission-store should be available at all applications or APIs I run, so that the Authorize attribute can do his job.
I think it shouldn't be so hard to do, so I'm missing a good working example how to implement something like this and what is working.
Who can help me with this to get this done?
I wrote some code to get the permissions by API call and use that in the IsInRole override. But when I declare it with the Authorize attr, it will not get in the method:
[ApiController]
1) [Authorize]
public class AuthController : ControllerBase
{
private readonly IdentityContext _context;
public AuthController(IdentityContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
[HttpGet()]
[Route("api/auth/isinrole")]
public bool IsInRole(string role)
{
2) if (User.FindFirst("sub")?.Value != null)
{
var userID = Guid.Parse(User.FindFirst("sub")?.Value);
if([This is the code that checks if user has role])
return true;
}
return false;
This is the IsInRole override (ClaimsPrincipal.IsInRole override):
public override bool IsInRole(string role)
{
var httpClient = _httpClientFactory.CreateClient("AuthClient");
3) var accessToken = _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken).Result;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var request = new HttpRequestMessage(HttpMethod.Get, "/api/auth/isinrole/?id=" + role);
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
etc...
This isn't working while it is not sending the access_token in the request
The 'sub' isn't send
Is always null
The open source version of the PolicyServer is a local implementation. All it does is read the permissions from a store (in the sample a config file) and transform them into claims using middleware.
In order to use the permissions you'll have to add this middleware in all projects where you want to use the permissions.
Having local permissions, you can't have conflicts with other resources. E.g. being an admin in api1 doesn't mean you are admin in api2 as well.
But decentralized permissions may be hard to maintain. That's why you probably want a central server for permissions, where the store actually calls the policy server rather than read the permissions from a local config file.
For that you'll need to add a discriminator in order to distinguish between resources. I use scopes, because that's the one thing that both the client and the resource share.
It also keeps the response small, you only have to request the permissions for a certain scope instead of all permissions.
The alternative is to use IdentityServer as-is. But instead of JWT tokens use reference tokens.
The random string is a lot shorter, but requires the client and / or resource to request the permissions by sending the reference token to the IdentityServer. This may be close to how the PolicyServer works, but with less control on the response.
There is an alternative to your solution and that is to use a referense token instead of a JWT-token. A reference token is just an opaque identifier and when a client receives this token, he has go to and look up the real token and details via the backend. The reference token does not contain any information. Its just a lookup identifier that the client can use against IdentiyServer
By using this your tokens will be very small.
Using reference token is just one option available to you.
see the documentation about Reference Tokens

Invalidating reference tokens from code (not http call to revocation endpoint)

I am persisting reference tokens to a db, my users have the ability to change or get a generated password. But if for example a user have forgotten their password and gets a new generated one then i would like to invalidate/remove all current tokens for this subject. Is it a good idea/acceptable to interact directly with the db via efcore or is there a api for this besides the /connect/revocation endpoint?
There is no problem in interacting with the database, but use the existing services to do this.
In IdentityService you can find the stores in the IdentityServer4.Stores namespace.
using IdentityServer4.Stores;
Inject the store in your controller:
private readonly IReferenceTokenStore _referenceTokenStore;
public class MyController : Controller
{
public MyController(IReferenceTokenStore referenceTokenStore)
{
_referenceTokenStore = referenceTokenStore;
}
}
And call it to remove the reference tokens for this user / client combination:
await _referenceTokenStore.RemoveReferenceTokensAsync(subjectId, clientId);
This will effectively remove the records from the database. You shouldn't create your own model of the database and remove the tokens directly.
Since IdentityServer is open source, you can take a look at the code that is used for token revocation.

How to update a Azure AD user?

I have successfully created a new user in Azure AD following Create User reference.
Now I want to update that very same user following Update User reference. To keep things simple This is the JSon content I'm sending:
{
"userType": "T"
}
The documentation tells me to send a PATCH request, but I always receive an HTTP Error 400 (Bad request). If I try sending a POST request I receive an HTTP Error 405 (Method not allowed).
The destination URL is https://graph.microsoft.com/v1.0/users/user-id.
The user-id is the one returned with the user details.
What am I missing?
Update 1
Those are the permissions set to the application:
This is exactly how you update user (PATCH).
However the userType property cannot be T, from the docs you refer:
That property can only have one of the two distinct values: Member or Guest.
Of course, the user-id in path should the id property as returned by the get users operation.
I am pretty sure that if you use a normal REST client will be able to see the whole error message, which will be more meaningful.
If you still have issue - paste the exact error message.

How can I redirect my views correctly with a SQL server Database? (not local database on the computer)

I have created an MVC application in visual studio 2013 using Visual Basic and when the user logs into the application it will either display the log In failed view or continue to the submit Issue view. All of this was correctly working when I used a local SQL Server database on the machine as it redirected correctly to all views.
But now that I have to publish the application and use an actual live SQL Database Sever not on the machine. The problem is that it will not redirect to the Submit Issue Page.
The Log In failure HTTP Post will return the Result number 200 (success) but the Submit Issue HTTP Post will return the Result number 302 (redirect Issue).
The application successfully can retrieve information from the live Database because when I compare the Log In details with the details in the Database like this:
Dim userdetailLocal = (From data In usertable.UserTables Where data.username = user.username AndAlso data.userPassword = user.userPassword Select data.username)
If (userdetailLocal.Count() > 0) Then
Return RedirectToAction("SubmitIssue")
Else
Return RedirectToAction("LogInFailure")
End If
This successfully navigates to either of the options when the details are incorrect or correct.
But only the "LogInFailure" View will successfully return like this:
Public Function LogInFailure() As ActionResult
Return View()
End Function
The "submitIssue" view will return this error:
"The page isn't redirecting properly. Firefox has detected that the server is redirecting the request for this address in a way that will never complete. This problem can sometimes be caused by disabling or refusing to accept cookies."
But the view is basically the same as the other the only difference is that the "submitIssue" view contains other information on it that is required to post an Issue. The only view that works that contains information required to post information is the "Log In" view.
Public Function SubmitIssue() As ActionResult
Return View()
End Function
<HttpPost()>
<AllowAnonymous>
<Authorize>
<ValidateAntiForgeryToken()>
Public Function SubmitIssue(<Bind(Include:="IssueID,IssueName,IssueSummary")>
ByVal issuetable As IssueTable, command As String, objModelMail As IssueTable) As ActionResult
Return Redirect("Success")
End Function
Then my connection string is:
add name="##connectionString##"
connectionString="metadata=res://*/IssueConnectionString.csdl|res://*/IssueConne ctionString.ssdl|res://*/IssueTracker.msl;
provider=System.Data.SqlClient;
provider connection string="
data source=SERVER\SQLSERVERTEST;
initial catalog=SERVERDATABASE;
persist security info=False;
user id=USER;
pwd=PWDID;
integrated security=False;
workstation id=WORKSTATION;
packet size=****;
MultipleActiveResultSets=True;
App=EntityFramework""
providerName="System.Data.EntityClient" />
I think it is to do with the permissions of the database but I am not sure does anyone know with I get this redirect error when using the Server Database but not a local database?
I also think that the redirect loop is coming from the browser successfully load the page again and again. But it cannot actually retrieve the view hence why FireFox is displaying that error message. The Network tool for the submitIssue view will return the following:
SubmitIssue
302 - POST - LogIn
200 - POST - abort?transport=longPolling&connectionToken=AQAAANC...
302 - GET - submitIssue
302 - GET - HttpError500
302 - GET - NotFound
302 - GET - NotFound (repeated infinitely)
LoginFailure
302 - POST - LogIn
200 - POST - abort?transport=longPolling&connectionToken=AQAAANC....
200 - GET - LogInFailure
200 - GET - broswerLink
200 - GET - negotiate?requestURL=http://...
I got my application working on the server by adding the following information to my application. The reason why I was getting a redirect loop was because there was a few bits of information missing that I needed to add that prevented the view from being correctly loaded.
In the submitIssue (GET Request) I added these viewbag's information:
ViewBag.issueTypeID = New SelectList(dbServer.IssueTypeTables, "IssueTypeID", "IssueTypeName")
ViewBag.priorityID = New SelectList(dbServer.PriorityTables, "priorityID", "severity")
This allowed the controller to read in the two ID's above which was one of the reasons that it was skipping over the view.
The second thing that I done to solve the issue was to add more information in the submitIssue (POST request).
Public Function SubmitIssue(<Bind(Include:="IssueID,IssueSummary,IssueTypeID,priorityID")>
issuetable As IssueTable, command As String, objModelMail As IssueTable) As ActionResult
Adding both of these to my applications enabled me to get all the views to successfully return Status of 200 (success) and prevent a redirect loop.

Resources