SharePoint High trust provider hosted app - user impersonation - Site minder - asp.net

We are using SiteMinder to authenticate user but all we get from site minder is user identity in header:
ASP.NET Authentication with Siteminder
However since we are using high trust provider hosted SharePoint app we have access to tokenHelper.cs but impersonating a user requires System.Security.Principal.WindowsIdentity
My questions are:
How to get WindowsIdentity in this case?
OR
How to extend tokenHelper to impersonate user just with user identity(without windowsIdentity)?

Check this blog by Steve Peschka. I have set up provider hosted app in SiteMinder protected SharePoint 2013 using that blog. To impersonate a user you need to create a ClaimsIdentity of the user and insert it to the HttpContext as current user. Sample code for that below:
var identity = new ClaimsIdentity(AuthenticationTypes.Federation, "http://schemas.xmlsoap.org/claims/useridentifier", String.Empty);
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/claims/useridentifier", userId, "http://www.w3.org/2001/XMLSchema#string"));
identity.AddClaim(new Claim(ClaimTypes.Email, smtp, "http://www.w3.org/2001/XMLSchema#string"));
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sip", nameIdentifier, "http://www.w3.org/2001/XMLSchema#string"));
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
Set this ClaimsPrincipalas the Httpcontext user.
The claim values to be passed are smtp= email of user , nameidentifier=loginname of user , userId= Account name of user

I will explain above scenario with my SP+Siteminder environment.
First of all you cant get the ClientContext of the site which is protected by site-minder.
You can only get clientContext of the site using internal url of site [http://hostname:port/sites/xyz].
To get the currenct user :-
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
// We store internal url of webapplication in web.config
string strAdminSiteURL = ConfigurationManager.AppSettings["AdminSiteURL"].ToString();
// We have written one function to convert site-minder url to internal url
string webUrl = Helper.Helper.GetInternalSiteUrl(strAdminSiteURL, spContext.SPHostUrl.ToString());
// Use internal url to create client-context
using (ClientContext clientContext = new ClientContext(webUrl))
{
clientContext.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
clientContext.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo(uName, pswd);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
// Load SP user from login name found from httpcontext
string currentSPUser = string.Concat("<<FBAIdentity>>", User.Identity.Name);
var currentUser = clientContext.Web.EnsureUser(currentSPUser);
clientContext.Load(currentUser);
clientContext.ExecuteQuery();
}
above code will work fine if authentication mode is FBA and will help you in getting current user.

Related

How to get IdentityUser by Username

I have previously worked with Membership through "System.Web.Security.Membership"
Here, you can do the following:
var currentUser = Membership.GetUser();
var otherUser = Membership.GetUser(username);
...giving you a MembershipUser.
Now, with Identity, I can find a load of ways to get the current logged in user.
But no way to get another user.
I can use:
var userStore = new UserStore<IdentityUser>();
var userManager = new UserManager<IdentityUser>(userStore);
var user = userManager.Find(username, password);
But that takes both username and password, with no overload for just username.
How do i get the IdentityUser from only a username?
Almost every answer I find is connected to MVC.
This is for a WCF service, where authorization is made using Identity. And in some cases the user is getting to the site from an other site with a generated "token" - an encrypted string, containing the username. From here, user is logged in and a session-cookie is set, depending on users settings.
Also, is there a shorter way to get UserInformation?
"var currentUser = Membership.GetUser(username);"
is much more convenient than
"var user2 = (new UserManager((new UserStore()))).Find(username, password);"
UserManager has UserManager<TUser>.FindByNameAsync method. You can try using it to find user by name.

Logout User From all Browser When Password is changed

I have a Reset Password page:
When the user fills the details and clicks the Reset Password button. The following controller is called:
public ActionResult ResetPassword(ResetPassword model)
{
...
return RedirectToAction("Logout");
}
When the user changes their password, they get Logged Out from the browser. However, if they are logged into another browser at the same time they remain logged in on the other browser.
I want to log out the user from all browsers they are logged into when they change their password.
I saw you are using ASP.NET Identity 2. What you are trying to do is already built in. All you need to do is change the SecurityStamp and all previous authentication cookies are no longer valid.
After you change the password you also need to change the SecurityStamp:
await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
await UserManager.UpdateSecurityStampAsync(User.Identity.GetUserId());
If you want the user to remain logged in, you have to reissue a new authentication cookie (signin):
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
Otherwise the user/session who initated the password change will also be logged out.
And to log out all other sessions immediately you need to lower the check interval in the config:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromSeconds(1),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
Steps to reproduce:
Created a new Asp.Net Web App in VS2015.
Choose MVC template.
Edit App_Stat/Startup.Auth.cs, line 34: change validateInterval: TimeSpan.FromMinutes(30) to validateInterval: TimeSpan.FromSeconds(1)
Edit Controllers/ManageController.cs, line 236: add the UserManager.UpdateSecurityStampAsync method call.
Run project, create a user, login, open a different browser and also login.
Change password, refresh the page in the other browser : you should be logged out.
So I got home and decided to put together some code. Show me the code !!!
I would use a handler so the verification is always done when the user first access the application and it is done at one place for every action method access.
The idea is when the user reset their password, the application records the user has reset their password and have not logged in for the first time and sign out the user.
user.HasResetPassword = true;
user.IsFirstLoginAfterPasswordReset = false;
When the user signs in, the application verifies if the user had previously reset their password and is now signing in for the first time. If these statements are valid the application updates its records to say you have not reset your password and you are not signing in for the first time.
Step 1
Add two properties to ApplicationUser model
Step 2
Add a class AuthHandler.cs in Models folder with the implementation below.
At this stage you verify if the user has reset their password and has not logged in for the first time since the password was reset. If this is true, redirect the user to the login.
Step 3
In RouteConfig.cs call the AuthHandler so that it is invoked for each incoming http request to your application.
Step 4
In ResetPassword method add implementation as below. At this step when a user has reset their password update the properties to say , they have reset their password and have not logged in for the first time. Notice the user is also signed out explicitly when they reset their password.
Step 5
In Login method add the implementation below. At this step if a user logins in successfully, verify their password was reset and they has logged for the first time is false. If all the conditions are true, update the properties in the database, so the properties are in a state ready for when the user resets the password in the future. So kind of a loop determining and updating the state of the password reset and first logins after resetting the password.
Lastly
Your AspnetUsers table should look as below
Comments
This is how I would approach it. I have not tested it so you may have modify it if you encounter exception. It is all also hard coded to show the approach to solved the problem.
Even ASP.NET Authentication says clearly that you have to have a secondary check to confirm if user is still an active logged in user (for example, we could block the user, user may have changed his password), Forms Authentication ticket does not offer any security against these things.
UserSession has nothing to do with ASP.NET MVC Session, it is just a name here
The solution I have implemented is,
Create a UserSessions table in the database with UserSessionID (PK, Identity) UserID (FK) DateCreated, DateUpdated
FormsAuthenticationTicket has a field called UserData, you can save UserSessionID in it.
When User Logs in
public void DoLogin(){
// do not call this ...
// FormsAuthentication.SetAuthCookie(....
DateTime dateIssued = DateTime.UtcNow;
var sessionID = db.CreateSession(UserID);
var ticket = new FormsAuthenticationTicket(
userName,
dateIssued,
dateIssued.Add(FormsAuthentication.Timeout),
iSpersistent,
// userData
sessionID.ToString());
HttpCookie cookie = new HttpCookie(
FormsAuthentication.CookieName,
FormsAuthentication.Encrypt(ticket));
cookie.Expires = ticket.Expires;
if(FormsAuthentication.CookieDomain!=null)
cookie.Domain = FormsAuthentication.CookieDomain;
cookie.Path = FormsAuthentication.CookiePath;
Response.Cookies.Add(cookie);
}
To Authorize User
Global.asax class enables to hook into Authorize
public void Application_Authorize(object sender, EventArgs e){
var user = Context.User;
if(user == null)
return;
FormsIdentity formsIdentity = user.Identity as FormsIdentity;
long userSessionID = long.Parse(formsIdentity.UserData);
string cacheKey = "US-" + userSessionID;
// caching to improve performance
object result = HttpRuntime.Cache[cacheKey];
if(result!=null){
// if we had cached that user is alright, we return..
return;
}
// hit the database and check if session is alright
// If user has logged out, then all UserSessions should have been
// deleted for this user
UserSession session = db.UserSessions
.FirstOrDefault(x=>x.UserSessionID == userSessionID);
if(session != null){
// update session and mark last date
// this helps you in tracking and you
// can also delete sessions which were not
// updated since long time...
session.DateUpdated = DateTime.UtcNow;
db.SaveChanges();
// ok user is good to login
HttpRuntime.Cache.Add(cacheKey, "OK",
// set expiration for 5 mins
DateTime.UtcNow.AddMinutes(5)..)
// I am setting cache for 5 mins to avoid
// hitting database for all session validation
return;
}
// ok validation is wrong....
throw new UnauthorizedException("Access denied");
}
When User Logs out
public void Logout(){
// get the ticket..
FormsIdentity f = Context.User.Identity as FormsIdentity;
long sessionID = long.Parse(f.UserData);
// this will prevent cookie hijacking
var session = db.UserSessions.First(x=>x.UserSessionID = sessionID);
db.UserSession.Remove(session);
db.SaveChanges();
FormsAuthentication.Signout();
}
When user changes password or user is blocked or user is deleted...
public void ChangePassword(){
// get the ticket..
FormsIdentity f = Context.User.Identity as FormsIdentity;
long sessionID = long.Parse(f.UserData);
// deleting Session will prevent all saved tickets from
// logging in
db.Database.ExecuteSql(
"DELETE FROM UerSessions WHERE UserSessionID=#SID",
new SqlParameter("#SID", sessionID));
}
The ASP.NET Identity authentication is dependent on cookies on the user's browser. Because you use two different browsers to test it. You will have two different authentication cookies.Until the cookies expire the user is still authenticated That is why you are getting that results.
So you will have to come with some custom implementation.
For instance, always check if the user's has reset the password and has not yet logged in for the first time with the new password. If they haven't, logout them out and redirect to login. When they login a new auth cookie will be created.
I modeled my approach around this article from Github's Blogs
Modeling your App's User Session
They use a Hybrid Cookie Store / DB approach using ruby but I ported it to My ASP .Net MVC project and works fine.
Users can see all other sessions and revoke them if needed. When a user resets password, any active sessions are revoked.
I use an ActionFilterAttribute on a base controller to check active sessions cookies. If session cookie is found to be stale the user is logged out and redirected to sign in.
Based on CodeRealm's answer...
For anyone who experiences a situation where https access to your application on the browser throws a null pointer exception (i.e Object reference not set to an instance of an object.), it is because there might be existing records in your database where HasResetPassWord and/or IsFirstLoginAfterPasswordReset is null. Http requests will work, but https requests will fail, not sure why.
Solution: Just update the database manually and give both fields values. Preferably, false on both columns.

logging in to ableCommerce via commerceBuilder api

i am trying to log in and authenticate with able Commerce?
i am using their DLLs (commerceBuilder)
i have tried
dim user as new commerceBuilder.users.user
user.username = "ABC"
user.password = "PASS"
user.adress = "www.websider.com"
does anyone know how to do this?
AbleCommerce makes use of ASP.NET Forms Authentication with custom membership provider. First validate the user and then if credentials are passed switch context user and set authentication cookie
if (Membership.ValidateUser(username, password))
{
var user = UserDataSource.LoadForUserName(username);
AbleContext.Current.User = user;
FormsAuthentication.SetAuthCookie(user.UserName, false)
}

How to Get Current User Principal

I want to use Windows Authentication and get User info such as Givenname, Surname, etc.
I used UserPrincipal.Current in IIS and I got an exception, but IIS express looks fine.
I solved by using a Find Method:
var domain = new PrincipalContext(ContextType.Domain);
var currentUser = UserPrincipal.FindByIdentity(domain, User.Identity.Name);

Limitation on using PrincipalContext & DomainContext, to retrive Active directory users

I have added the following code inside my asp.net mvc web application model class, to retrive the current AD users:-
public List<DomainContext> GetADUsers(string term=null)
{
List<DomainContext> results = new List<DomainContext>();
string ADServerName = System.Web.Configuration.WebConfigurationManager.AppSettings["ADServerName"];
using (var context = new PrincipalContext(ContextType.Domain, ADServerName))
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
var searchResults = searcher.FindAll();
foreach (Principal p in searchResults)
{
if (term == null || p.SamAccountName.ToString().ToUpper().StartsWith(term.ToUpper()))
{
DomainContext dc = new DomainContext();
dc.DisplayName = p.DisplayName;
dc.UserPrincipalName = p.UserPrincipalName;
dc.Name = p.Name;
dc.SamAccountName = p.SamAccountName ;
dc.DistinguishedName = p.DistinguishedName;
results.Add(dc);
}
}
}
return results;
}
I am now on the development machine , where AD is on the same machine as the asp.net mvc web application runs. And there is no need to provide username or password to access the AD. But I have the following questions about using my above approach on production server :-
Will the same approach work well if the AD and the asp.net mvc (deployed on IIS ) are not on the same machine?
Will I be able to provide username and password to access the active directory?
What are the general requirements I should achieve to be able to allow the Domaincontext class to access AD on remote servers ?
Thanks I advance for any help.
Regards
I think you're asking if you're able to use the same code if the web server is not apart of the Active Directory domain. PrincipalContext does have an overload for username and password to allow for credentials to be used to connect, instead of relying on the machine having enough permissions to read from the directory.
As for permissions, grant as few as possible. I would get your system administrator involved to create a the account. You maybe able to use Service Accounts which were introduced in Windows Server 2008 to allow for the authentication to happen.

Resources