Admin can disable or suspend user's entrance.
We can check "is user disabled" in "user login".
("Checking Db for each request" is not good option)
So we want to remove user's session when admin disables it's account.
How can we achieve it?
If you know or have kept the sessionId you can remove a session from the cache with:
using (var cache = TryResolve<ICacheClient>())
{
var sessionKey = SessionFeature.GetSessionKey(sessionId);
cache.Remove(sessionKey);
}
But ServiceStack doesn't keep a map of all User's Session ids itself. One way to avoid DB lookups on each request is when disabling the account keep a record of the disabled User Ids which you can later validate in a global Request Filter to ensure the user isn't locked.
Best way to store the locked user ids is in the cache that way the visibility and lifetime of the locked user ids is in the same cache storing the sessions. You can use a custom cache key to record locked user ids, e.g:
GlobalRequestFilters.Add((req, res, dto) =>
{
var session = req.GetSession();
using (var cache = TryResolve<ICacheClient>())
{
if (cache.Get<string>("locked-user:" + session.UserAuthId) != null)
{
var sessionKey = SessionFeature.GetSessionKey(session.Id);
cache.Remove(sessionKey);
req.Items.Remove(ServiceExtensions.RequestItemsSessionKey);
}
}
});
This will remove the locked users sessions the next time they try to access ServiceStack, forcing them to login again at which point they will notice they've been locked out.
A new RemoveSession API was added in this commit which makes this a little nicer (from v4.0.34+):
if (cache.Get<string>("locked-user:" + session.UserAuthId) != null)
req.RemoveSession(session.Id);
Related
I use MongoDB to store user data. The user id goes incrementally, such as 1, 2, 3, 4 etc when new user register.
I have the following code to generate the user id. "users" is the name of the collection where I store the user data.
// generate new user id
let uid;
const collections = await db.listCollections().toArray();
const collectionNames = collections.map(collection => collection.name);
if(collectionNames.indexOf("users") == -1){
uid = 1;
}
else{
const newest_user = await db.collection("users").find({}).sort({"_id":-1}).limit(1).toArray();
uid = newest_user[0]["_id"] + 1;
}
user._id = uid;
// add and save user
db.collection("users").insertOne(user).catch((error)=>{
throw error;
});
One concern I have now is that when two users make a request to register at the same time, they will get same maximum user id, and create the same new user id. One way to prevent it is using a locked thread. But, I think Node.js and Next.js doesn't support multi-thread.
What are some alternatives I have to solve this problem?
In addition, _id will be the field for uid. Will it make a difference since _id can't be duplicated.
Why not have the database generate the auto-incrementing ID? https://www.mongodb.com/basics/mongodb-auto-increment
One idea I have is using a transaction which can solve the concurrency issue. Transactions obey the rule of ACID. The writes to the database from the concurrent requests will run in isolation.
I would like to be able to sign out some user that is logged in. By this I do not mean current user but some other user.
I, as an administrator, can deactivate or ban a user. At this point I would like to get this user and sign him out so when he makes his next request he gets redirected to log in page. After login attempt he would get a message why he cannot log in.
I am aware I can do it by writing my own authorization filter and go and fetch user from database each time but I would, if possible, like to avoid that.
Setting up authentication as follows hits mz OnValidatePrincipal each time a request is made but I do not know how to update exactly this user.
services.AddAuthentication("CookieAuthentication")
.AddCookie("CookieAuthentication", options => {
options.LoginPath = "/Login";
options.Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = new Func<CookieValidatePrincipalContext, Task>(async (a) =>
{
var expired = a.Principal.FindFirst(System.Security.Claims.ClaimTypes.Expired);
if(expired != null && bool.Parse(expired.Value))
{
a.RejectPrincipal();
return;
}
await Task.CompletedTask;
})
};
});
I am also aware that I could write my own middleware and keep some kind of list of all the users that have logged in. That list could than be easily updated by an administrator and checked by the previous event.
What I would like to know is, can I get an instance of other logged user and can I than alter its claims?
I'm writing an application which needs to have personally identifiable information removed/absent at all times from the database. Given that someone may use their real name in their username, and that an email address may be present in their AspUserIdentity records, I have decided one solution might be to hash these values. In simple terms: when someone logs in with a username, I hash the username they entered and see if that hash exists in the database; if it does, then I log them in. This is easy to do and works just fine by modifying the Login and Register methods in the AccountController. But now I am left with no knowledge of the entered username...
I could just store the username in session, but that seems jankety. What I'd like to do is to update the cookie that gets sent down upon successful login to use the username they entered (and not the hashed value stored in the DB). That way User.Identity.GetUserName() returns the plain text username (and not the hashed username). To the client the process ought to be transparent (and to me as the programmer too).
The question is: how? What's the best place to do this? I'm still relatively green when it comes to the latest ASP.NET Identity stuff. I see in Startup.Auth there's a lot of juicy stuff related to cookies, but I don't see anywhere I can modify the cookie itself upon login and prior to it being sent down.
Is all of this deep within Owin itself?
Thanks in advance,
When user logs in and you compare the hash of username, you can add their real username as a claim to the identity. This is serialised into cookie and available with the user on every request, but not persisted in a DB:
public async Task SignIn(string userName, string password, bool rememberMe)
{
var hashedUsername = getMyHash(username)
var loggedInUser = await userManager.FindAsync(hashedUsername, password);
if (loggedInUser == null)
{
// failed to login
return FailedToLogin(); // whatever you do there
}
// Ok, from now on we have user who provided correct username and password.
// and because correct username/password was given, we reset count for incorrect logins. This is for user lockout
await userManager.ResetAccessFailedCountAsync(loggedInUser.Id);
if (!loggedInUser.EmailConfirmed)
{
return EmailIsNotConfirmed(); // email is not confirmed - display a message
}
if (await userManager.IsLockedOutAsync(loggedInUser.Id))
{
return UserLockedOut(); // user is locked out - display a message
}
var identity = await userManager.CreateIdentityAsync(loggedInUser);
identity.AddClaim(new Claim("OriginalUsername", originalUsername));
var authenticationManager = context.GetOwinContext().Authentication;
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = rememberMe }, identity);
//TODO redirect to a home page
}
Then when you need to display an actual username, not a hash do this:
public static String GetOriginalUsername(this IPrincipal principal)
{
if (principal == null)
{
return String.Empty;
}
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
return String.Empty;
}
var originalUsernameClaim = principal.Claims.SingleOrDefault(c => c.Type == "OriginalUsername");
if (originalUsernameClaim == null)
{
return String.Empty;
}
return originalUsernameClaim.Value;
}
And call this method on User.GetOriginalUsername() in *.cshtml files or in Controllers. Or HttpContext.Current.User.GetOriginalUsername() if you need it somewhere else.
I am using a library to authenticate LDAP Users, whose code is as follows:
public void authUser(String username, String pwd)
throws Exception
{
try
{
Properties env = getEnvironmentForContext();
env.put("java.naming.security.principal", "uid=" +
username + ",ou=users, dc=company"));
env.put("java.naming.security.credentials", pwd);
context = getContext(env);
System.out.println("Authentication Succeeded");
}
catch (Exception e)
{
System.out.println("Authentication Failed");
throw e;
}
}
Please note, i cannot modify the above Authentication Code. It comes from a external Library.
But, i want to deactivate some users (not delete them), so that Authentication Fails.
I am using LDAP (not Active Directory). Do not know what LDAP Software it is though, i can connect to it using 'LDAP Browser Client'.
The users exist under: dc=company, ou=users, uid=username
What attribute can i add/change on LDAP 'user' to de-activate a user.
Could i move the user to a different group like: dc=company, ou=deactivatedusers, uid=username? But this is not the preferred option, plus am not sure best way to do that.
EDIT: The LDAP being used is: Netscape/Sun/iPlanet
To answer your question per the Oracle iPlanet (Sun) documentation :
Setting the attribute nsAccountLock to true will disable a users account, and prevent them from binding to the directory.
However, in terms of the code you already have, I just don't see any way of accomplishing this... Is there something preventing you from writing your own implementation for iPlanet using the System.DirectoryServices.Protocols namespace in .Net?
Here is how I bind and authorize users against an iPlanet server :
//Build servername from variables
var BuildServerName = new StringBuilder();
BuildServerName.Append(ServerName);
BuildServerName.Append(":" + Convert.ToString(Port));
var ldapConnection = new LdapConnection(BuildServerName.ToString());
//Authenticate the Admin username and password, making sure it's a valid login
try
{
//Pass in the network (administrative) creds, and the domain.
var networkCredential = new NetworkCredential(Username, Password, config.LdapAuth.LdapDomain);
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
ldapConnection.AuthType = AuthType.Anonymous;;
ldapConnection.Bind(networkCredential);
//Lets find this person so we can use the correct DN syntax when we authorize them.
SearchRequest FindThem = new SearchRequest();
FindThem.Filter = config.LdapAuth.LdapFilter.Replace("{{Patron}}", Patron);
FindThem.DistinguishedName = config.LdapAuth.LdapDomain;
FindThem.Scope = System.DirectoryServices.Protocols.SearchScope.Subtree;
//We'll execute a search using the bound user
SearchResponse searchresults = (SearchResponse) ldapConnection.SendRequest(FindThem);
//Should only get on result back, if not throw an error
if(searchresults.Entries.Count == 1)
{
SearchResultEntryCollection entries = searchresults.Entries;
SearchResultEntry thispatron = entries[0];
PatronDN = thispatron.DistinguishedName;
}
}
If you wanted to move disabled users to a specific group, from this point you could write logic to check the DistinguishedName of that user, and throw a handled exception if their DistinguishedName contains the name of that group. Also, if the nsAccountLock attribute is available to your binding account as a readable attribute, you could just check the value of that attribute for true, and handle the user accordingly.
Here is the java code for disabling and enabling user in Active Directory using JNDI.
Make sure to connect with your AD before calling below code.
public void disableEnableUser() throws Exception {
ModificationItem[] mods = new ModificationItem[1];
//To enable user
//int UF_ACCOUNT_ENABLE = 0x0001;
//mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_ACCOUNT_ENABLE)));
// To disable user
int UF_ACCOUNT_DISABLE = 0x0002;
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_ACCOUNT_DISABLE)));
ctx.modifyAttributes("CN=John ABC,OU=Users,OU=anyone,DC=yourcompanyname,DC=com", mods);
}
Distinguished name = "CN=John ABC,OU=Users,OU=anyone,DC=yourcompanyname,DC=com"
This name is depend on your structure of Active Directory, you can confirm from your suport team.
If the directory software supports a password policy feature, it probably provides attributes to lock/deactivate the user. If not, you can simply nullify the password attribute (e.g., userpassword). The LDAP server should return the "inappropriate authentication" error to the client when the authenticated bind is performed.
You could just change the user's password. If it's OpenLDAP with the password-policy overlay, or another LDAP server that supports locking, you can lock the user as well. You really will have to find out.
I am evaluating ServiceStack using OrmLite. The built in Auth service, along with Session and Cache are so much better than ASP.NET membership provider.
However, out of the box the Auth Service does not provide some of the features required for apps we want to build like:
Change password
Locking of account after 3 unsuccessful logon attempts
Disabling user accounts
Password reminder question and answer
Audit log of log on attempts
Do I need to build custom auth provider or is there something out there which already does provides this functionality?
Many thanks!
I'm just starting to implement a password reset and can see two ways of achieving it (I've not tested - or even tried - either yet):
1.Create a class that inherits from Registration and handles PUT. It should then be possible to call the UpdateUserAuth method of the registration class which would change the password. The problem - for me - here is that the put validation requires username and password to be specified, not just one (We only use email as an identifier). This could be worked around by turning the validation feature off.
2.Create a password reset service that does what UpdateUserAuth does.
var session = this.GetSession();
var existingUser = UserAuthRepo.GetUserAuth(session, null);
if (existingUser == null)
{
throw HttpError.NotFound("User does not exist");
}
var newUserAuth = ToUserAuth(request);
UserAuthRepo.UpdateUserAuth(newUserAuth, existingUser, request.Password);
Obviously need to add some appropriate validation in.
UPDATED
I've put my change password reminder/reset service up as a gist (My first gist!)
here's what I did, works well. - I realise the "new" is a code-smell, just inject it :)
private int LoginAttempts = 0;
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = authService.TryResolve<IUserAuthRepository>();
if (authRepo == null)
{
Log.WarnFormat("Tried to authenticate without a registered IUserAuthRepository");
return false;
}
var session = authService.GetSession();
UserAuth userAuth = null;
if (authRepo.TryAuthenticate(userName, password, out userAuth))
{
session.PopulateWith(userAuth);
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserOAuthProviders(session.UserAuthId)
.ConvertAll(x => (IOAuthTokens)x);
return true;
}
else
{
LoginAttempts++;
if (LoginAttempts >= 3)
{
ServiceStack.ServiceInterface.Service s = new Service();
s.Db.ExecuteSql("update [User] set AccountLocked = 'true' where Email='" + userName + "'");
}
authService.RemoveSession();
return false;
}
}
and I hope the mod_from_hell manages to leave this alone!!!