Permission to provision new site collection through workflow - access-denied

I am stuck onto a very strange situation.
I have a workflow that i use to provision new site on my web application. This workflow uses one custom workflow activity to provision the site using followoing statement.
---other code omited for clarity----
SPSiteCollection.Add()
This statement is throwing followign exception when my applicaiton pool account is not same as Central Admin applicaiton pool account.
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
at
Microsoft.SharePoint.SPGlobal.HandleUnauthorizedAccessException(UnauthorizedAccessException
ex) at
Microsoft.SharePoint.Library.SPRequest.CreateSite(Guid
gApplicationId, String bstrUrl, Int32
lZone, Guid gSiteId, Guid gDatabaseId,
String bstrDat
after a lot of googling and findings i have zeroed down to the Applicaiton Pool account permission.
The workflow code always runs under the System account (the applicaiton pool identity). In order to create new SharePoint site collection, the application pool requires access to "SharePoint_Config" database.
When my web application is running under the applicaiton pool credential of Central Admin, it has all the access to the configuration database. But when i am running the under any other applicaiton pool identity which has less permission. it throws exception, even if i give DBO permission to the applicaiton pool account in the Configuration database.
My applicaiton event log has following entry :-
Event Source: Windows SharePoint
Services 3 Event Category: Database
Event ID: 3760 Date: 2/3/2010
Time: 2:36:16 AM User: N/A
Computer: SHAREPOINT20 Description:
SQL Database 'SharePoint_Config' on
SQL Server instance 'houspsr001' not
found. Additional error information
from SQL Server is included below.
Cannot open database
"SharePoint_Config" requested by the
login. The login failed. Login failed
for user 'DOMAIN\WebAppPool'.
For more information, see Help and
Support Center at
http://go.microsoft.com/fwlink/events.asp.
My question is...is it mendatory to run such code under the applicaiton pool account of central admin.
Any workaround for this....?
My question

Finally the access denied issue has been resolved. As I motioned in my previous email, the issue was due to insufficient permission to my application pool identity.
Central Admin was running under a different application pool identity
Web applications are running under a different application pool identity.
My workflow was using the ElevatedPrevilages to provision a site collection, and it used to get Access Denied from the database since it did not had permission to modify SharePoint_Config database.
Resolution
In order to resolve this issue i had to impersonate the application pool identity of Central Admin. Here is the required method for impersonating Central Admin application pool user.
#region Application Pool Identity Impersonate
protected static WindowsIdentity CreateIdentity(string User, string Domain, string Password)
{
// The Windows NT user token.
IntPtr tokenHandle = new IntPtr(0);
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_NETWORK = 3;
tokenHandle = IntPtr.Zero;
// Call LogonUser to obtain a handle to an access token.
int returnValue = LogonUser(User, Domain, Password,LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT,out tokenHandle);
//Check if the logon user method succeeded
if (returnValue <= 0)
{
int ret = Marshal.GetLastWin32Error();
throw new Exception("LogonUser failed with error code: " + ret);
}
//System.Diagnostics.Debug.WriteLine("Created user token: " + tokenHandle);
//The WindowsIdentity class makes a new copy of the token.
//It also handles calling CloseHandle for the copy.
WindowsIdentity id = new WindowsIdentity(tokenHandle);
CloseHandle(tokenHandle);
return id;
}
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int ImpersonateLoggedOnUser(
IntPtr hToken
);
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RevertToSelf();
[DllImport("kernel32.dll", SetLastError = true)]
static extern int CloseHandle(IntPtr hObject);
#endregion
And then my code to create site collection looks like:-
//Impersonate the logged in user, ApplicationUser, LoginDomain and Password are exposed as property of the class.
WindowsImpersonationContext wiContext = CreateIdentity(this.ApplicationPoolUser, this.LoginDomain, this.SystemPassword).Impersonate();
//Provision new site collection and update the property for new site collection url.
using (SPSite newSiteCollection = spSiteColl.Add(SUGGESTEDURL, TITLE, DESC, LCID, WEBTEMPLATE, PRIMARYOWNER.LoginName, PRIMARYOWNER.Name, PRIMARYOWNER.Email, SECONDARYOWNER.LoginName, SECONDARYOWNER.Name, SECONDARYOWNER.Email))
{
this.SUGGESTEDURL = newSiteCollection.Url;
}
//Reset the impersonation.
wiContext.Undo();

As Im not allowed to comment on Sudhir's answer, Im posting my remark as an answer. I used basically the same code which Sudhir proposes as a solution. The impersonation works, but it has a security flaw. If you store your password as a plain text (managed) string it may be moved within memory and even stored to the hard disk because of paging. This makes it easy for aunothrized persons to spy your credentials.
It is therefor recommended to use SecureString for this purpose. How this can be used in conjunction with SecureString can be looked up on MSDN. The main difference to Sudhir's solution is to use a different overload of LogonUser, namely
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(String username, String domain,
IntPtr password, int logonType,
int logonProvider, ref IntPtr token);
and use it like this (this code is from MSDN):
// Marshal the SecureString to unmanaged memory.
passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(pwdSecureString);
// Call LogonUser, passing the unmanaged (and decrypted) copy of
// the SecureString password.
returnValue = LogonUser(userName, domainName, passwordPtr,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle);
// Zero-out and free the unmanaged string reference.
Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
This way the password is only encrypted right before we use it to do the user login. Afterwards the plaintext password is freed from memory immediatelly.

Related

How to include a user manager to another application for ASP.NET Core 3.1

I'm developing two different applications, I will name them A and B.
A is an internet platform, where you can logon only if you have a valid user account.
B is an intranet platform, where users can authenticate via Active Directory. An administrator using application B should be able to create new user accounts for application A.
After the creation of a new user account, I want to be able to realize different functions, for example to send an e-mail to the registered mail address, so the new user can change the default password.
All the functionalities that I want to implement, can be done by the UserManager (see section "Use another app to add users" in the following link: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-3.1&tabs=visual-studio#disable-register-page).
Based on this I implemented the following code:
public class ControllerClass : Controller
{
private readonly HelperClass _helper;
private readonly UserManager<IdentityUser> _userManager;
public ControllerClass (UserManager<IdentityUser> userManager)
{
_userManager = userManager;
_helper= new HelperClass (userManager);
}
}
public class HelperClass
{
private readonly DbContext _db;
private readonly UserManager<IdentityUser> _userManager;
public HelperClass (UserManager<IdentityUser> userManager)
{
_db = new DbContext ();
_userManager = userManager;
}
private async Task<string> EnsureUser(string userName, string userPassword)
{
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
user = new IdentityUser()
{
UserName = userName
};
await _userManager.CreateAsync(user, userPassword);
}
return user.Id;
}
internal async void CreateUser(UserVM uvm, int id)
{
var userId = await EnsureUser(uvm.userName, uvm.userPassword);
// TODO ...
}
}
Unfortunately I didn't manage to include the UserManager into my application B. I got the following error message: "An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[IdentityUser]' while attempting to activate 'ControllerClass '."
Do you have an idea, how I can add the UserManager to manage the users for another application?
Well, the specific error you're getting is simply because UserManager<TUser> is not registered in the service collection. In order to inject anything, you must first register it. In your actual user-facing app, that's being done by services.AddIdentity(). However, that does a lot more than just register UserManager<TUser>, so you shouldn't just run off and add the same command to your second app.
You could add a registration for it specifically:
services.AddScoped<UserManager<ApplicationUser>>();
However, it actually has a ton of dependencies, each of which would also need to be registered. If you look at the constructor, there's seven services not registered out of the box, many of which have their own dependency trees. Long and short, it's a pain. There's also the matter of separation of concerns, here. This would require adding in the whole data layer from the other app.
Your best bet is to simply expose an API on the Identity app (and lock it down, of course). That way, all the logic of working with users stays with the rest of that logic. The administration app, then, can call out to the API to add, update, delete, etc. users without having to have knowledge of how that's actually done.
Answering after 2 years. For future reader, You can use
services.AddIdentityCore<IdentityUser>();
which adds necessary services that are for user-management add/delete etc. without adding Login service.
to add EntityFramework you can create context and use like this
services.AddDbContext<ApplicationAuthDbContext>(options =>
{
// Configure the context to use postgresql.
options.UseNpgsql(config.GetConnectionString("AuthDbContext"))
.UseSnakeCaseNamingConvention();
});
services.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<ApplicationAuthDbContext>();
For more information
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.identityservicecollectionextensions.addidentitycore?view=aspnetcore-6.0

How can the resource server identify the resource owner using token in oauth2?

The typical scenario I am looking into is:
User1 provides proper credentials to the front-end rest client (grant type: password) and the client gets the token in return.
The client sends the token and accesses the resources owned by User1.
In my scenario, once the client has the access token for user1, I want the client to have access limited to User1's resources only.
Consider that the client accesses the URI /users/1/books. The response will contain all the books associated with User1. The main problem is that if the client accesses the URL /users/2/books with User1's token, it gets the list of all the books for User2 which shouldn't be allowed.
How can I limit the scope to the user whose credentials were used to obtain the token?
How can I map the token to a specific user in my resource server?
I am using Spring/Java. But any general theory will also help.
After a lot of debugging, I got the answer.
Spring security 1.4
Token store: InMemoryTokenStore()
In ResourceServerConfiguration, configure HttpSecurity.
#Override
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().
// antMatchers("/oauth/token").permitAll().
antMatchers("/api/users/{userId}").access("#webSecurity.checkUserId(authentication,#userId)")
.anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();
// #formatter:on
}
Create a class WebSecurity and provide the implementation.
public class WebSecurity {
public boolean checkUserId(Authentication auth, int id) {
return ((UmUser)auth.getPrincipal()).getId() == id;
}
}
Resources:
http://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html#el-access-web-path-variables
http://www.baeldung.com/get-user-in-spring-security
I had to debug a lot as I was using JwtTokenStore. This returned the Principal as a String and not the instance of UserDetails as I was expecting.
Once I switched to InMemoryTokenStore, I got the expected results. This was not a problem for me as I had the choice, but I would still like to know how to achieve it with the JWT.

rest api unauthorized Azure Data Catalog

I am using Azure Data Catalog of my organization. I am not creator/administrator/owner of the Catalog but I have access to register/delete catalogs from the web interface.
I want to use rest API for Azure Data Catalog. Is it possible with my level of permission?
I have followed all the steps from https://msdn.microsoft.com/en-us/library/mt428033.aspx and written the following piece of code:
class Program
{
static void Main(string[] args)
{
string url = "https://api.azuredatacatalog.com/catalogs/DefaultCatalog/search/search?searchTerms=My_Server&count=10&startPage=1&api-version=2016-03-30";
HttpWebRequest request = System.Net.WebRequest.Create(url) as System.Net.HttpWebRequest;
Console.WriteLine(AccessToken().CreateAuthorizationHeader());
try
{
WebResponse response = request.GetResponse();
Console.WriteLine(response.ContentLength);
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadKey();
}
static AuthenticationResult AccessToken()
{
string resourceUri = "https://datacatalog.azure.com";
string clientId = "my-client-id";
string redirectUri = "my-redirect-uri";
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
return authContext.AcquireToken(resourceUri, clientId, new Uri(redirectUri), PromptBehavior.RefreshSession);
}
}
and when I try to run the search from API, I get the following error:
System.Net.WebException: The remote server returned an error: (401)
Unauthorized. at System.Net.HttpWebRequest.GetResponse() at
HRBIADCAPI.Program.Main(String[] args) in
c:\users\naghimir\documents\visual studio
2015\Projects\HRBIADCAPI\HRBIADCAPI\Program.cs:line 32
Now I think the problem is that I have not given access to the client program created to read/write data catalog (that I did in Azure Data Factory) but that step is not there in the documentation either.
Do I need to be the owner or can I request permission from the owner to use Azure Data Catalog API?
Based on the description, you were using the OAuth 2.0 code grant flow to grant the app to delegate the user to manipulate the Azure Data Catalog.
To ensure the request works well, we need to grant the scope to the app like figure below:
And since the app only delegate the users’ permission, please ensure that user have the sufficient permission to operate the resource manfully.

Assign site template to organization site programmatically in liferay 6.1.0

I have created user & organization pragmatically using addUser() & addOrganization() methods respectively.
I am also able to add users to this organization using addOrganizationUsers() method.
Now I have created a site template from liferay control panel.
As we know , we can create a site for organization, and while creating a site we have options to select a site template for public & private pages.
As we know .
Public page - Visible to members + non members
Private page - Visible to only members.
So I want to create a organization site with private pages only so it will be seen by only organization member.
OrganiztionLocalServiceUtil.addOrganization(
long userId, long parentOrganizationId, String name, String type,
boolean recursable, long regionId, long countryId, int statusId,
String comments, boolean site, ServiceContext serviceContext)
Using above method , by specifying boolean site value 'true' a site will get created.
Now I want to add a site template to this organization site pragmatically which I have created from control panel.
So is there any API to add site template to any site of organization
Unfortunately there is no public API for it.
Use LayoutSetPrototypeLocalServiceUtil to get the ID for the SiteTemplate. To get the SiteTemplate by name you'll have to either use a dynamicQuery or iterate over the result of LayoutSetPrototypeLocalServiceUtil.getLayoutSetPrototypes(-1, -1)
Then invoke applyLayoutSetPrototypes of SitesUtil in the context of the portal.
MethodKey methodKey = new MethodKey("com.liferay.portlet.sites.util.SitesUtil","applyLayoutSetPrototypes", Group.class, long.class, long.class, ServiceContext.class);
PortalClassInvoker.invoke(false, methodKey, organization.getGroup(), publicLayoutSetId, privateLayoutSetId, serviceContext);
Specify -1 for publicLayoutSetId.
An Admin has to be logged in to perform this action.
To perform this action on startup or in the background a new ServiceContext would be needed.
Something like the following
ServiceContext serviceContext = new ServiceContext();
serviceContext.setAddGroupPermissions(true);
serviceContext.setAddGuestPermissions(true);
serviceContext.setSignedIn(false);
// set the following to an admin user / company or default user
User user = UserLocalServiceUtil.getDefaultUser(companyId); // or any user that has the permissions
serviceContext.setUserId(user.getUserId());
serviceContext.setCompanyId(companyId);
And most likely you also have to setup the ThreadPermissionChecker
PrincipalThreadLocal.setName(user.getUserId());
PermissionChecker adminPermissionChecker = PermissionCheckerFactoryUtil.create(user, false);
PermissionThreadLocal.setPermissionChecker(adminPermissionChecker);
Don't forget to reset the permission checker in a final block otherwise the same permission checker might be used for other requests on the same thread.

ReportViewer control using ReportServerCredentials.NetworkCredentials

I'm using the ReportViewer control to access a SSRS 2008 Report from a VS2010 ASP.NET MVC 2 Web Application deployed on IIS7 with the following setup:
Forms Authentication and Anonymous Authentication enabled
ASP.NET Impersonation disabled
App pool identity configured to use local user account that has access rights to specific folders on the server required by the application
The webserver is not part of the domain but the SSRS server is on the domain
Since I need to use separate credentials to access SSRS Reports, I have implemented IReportServerCredentials as explained here:
http://msdn.microsoft.com/en-us/library/microsoft.reporting.webforms.ireportservercredentials(v=vs.100).aspx
The problem I'm having is it always goes to IReportServerCredentials.ImpersonationUser instead of IReportServerCredentials.NetworkCredentials where I need it to go because I'm retrieving the required credentials off web.config.
Maybe I'm missing something simple here but I've tried different combination of these settings and have had no luck. Any pointers on how I could get this working would be much appreciated!
My code:
[Serializable]
public sealed class MyReportServerCredentials :
IReportServerCredentials
{
public WindowsIdentity ImpersonationUser
{
get
{
// Use the default Windows user. Credentials will be
// provided by the NetworkCredentials property.
return null;
}
}
public ICredentials NetworkCredentials
{
get
{
// Read the user information from the Web.config file.
// By reading the information on demand instead of
// storing it, the credentials will not be stored in
// session, reducing the vulnerable surface area to the
// Web.config file, which can be secured with an ACL.
// User name
string userName =
ConfigurationManager.AppSettings
["MyReportViewerUser"];
if (string.IsNullOrEmpty(userName))
throw new Exception(
"Missing user name from web.config file");
// Password
string password =
ConfigurationManager.AppSettings
["MyReportViewerPassword"];
if (string.IsNullOrEmpty(password))
throw new Exception(
"Missing password from web.config file");
// Domain
string domain =
ConfigurationManager.AppSettings
["MyReportViewerDomain"];
if (string.IsNullOrEmpty(domain))
throw new Exception(
"Missing domain from web.config file");
return new NetworkCredential(userName, password, domain);
}
}
public bool GetFormsCredentials(out Cookie authCookie,
out string userName, out string password,
out string authority)
{
authCookie = null;
userName = null;
password = null;
authority = null;
// Not using form credentials
return false;
}
}
this.MyReportView.ServerReport.ReportServerCredentials = new MyReportServerCredentials();
Thanks
I implement this same class on every reporting page I need and it works correctly.
Check your web.config file.
I have:
<configuration>
<system.web>
<authentication mode="Windows"/>
</configuration>
</system.web>
Yes. Authentication mode "Windows".
However, I have worked also using Forms Authentication.
So I think this is more a permissions issue.
Check this post:
UAC Windows 7 (Proffesional) 64 bit and SSRS 2008 R2 (10.50.1617) 64 bit
Also this post:
Reporting Services 2008: asking for username and password