I am attempting to migrate users from Drupal 8 into firebase auth.
I have tried following the instructions on https://firebase.google.com/docs/auth/admin/import-users#import_users_with_md5_sha_and_pbkdf_hashed_passwords.
The users import successfully, but the password does not work when I attempt to log in. After reversing the Drupal code, I'm getting the feeling that the sha512 hashing mechanism provided by firebase is not doing the same steps as what drupal does to determine the hash of the password.
The data in the drupal database for one user (this is dev data, not real user)
password: $S$EF//ORKHHZKG9L4UEUUNycm0v5HatfjQxkxbKn19BiYMsPxi3u68
From reading through the drupal code, I've determined the following
$S$ = SHA512
"E" = 16 rounds
"F//ORKH" = the salt
"HZKG9L4UEUUNycm0v5HatfjQxkxbKn19BiYMsPxi3u68" = the hash
This was taken from here: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Password%21PhpassHashedPassword.php/class/PhpassHashedPassword/8.7.x
The relevant Go code for the import:
users := []*auth.UserToImport{
(&auth.UserToImport{}).
UID("00048ebbb178d47f674f48485205235c").
Email("CPIZFTPX#mailinator.com").
PasswordHash([]byte("HZKG9L4UEUUNycm0v5HatfjQxkxbKn19BiYMsPxi3u68")).
PasswordSalt([]byte("F//ORKH")),
}
h := hash.SHA512{
Rounds: 16,
}
result, err := client.ImportUsers(context.Background(), users, auth.WithHash(h))
So... with all of that said, i think the issue is that the drupal code is truncating the string that is actually being stored in the database to 55 characters. You can see this in the last line of the crypt method in the drupal code (url pasted above).
Has anyone out there successfully migrated drupal 8 users to firebase? If so, I'd love to know what step im missing. If not, some confirmation of my findings would help with my sanity.
From looking at this, It doesn't look like that is a SHA. Most systems encode the Bytes of the SHA into a string for ease of storage. Looking at the Drupal code file you posted, it looks like they base64 encode the SHA.
In this case, I believe it needs to be decoded before being passed into PasswordHash
Try adding:
decoded, err := base64.StdEncoding.DecodeString("HZKG9L4UEUUNycm0v5HatfjQxkxbKn19BiYMsPxi3u68")
Then just pass it into the rest of your code:
users := []*auth.UserToImport{
(&auth.UserToImport{}).
UID("00048ebbb178d47f674f48485205235c").
Email("CPIZFTPX#mailinator.com").
PasswordHash(decoded).
PasswordSalt([]byte("F//ORKH")),
}
h := hash.SHA512{
Rounds: 16,
}
result, err := client.ImportUsers(context.Background(), users, auth.WithHash(h))
I don't know what the password is, or I would have tested it out on my firebase account.
Related
I tried to search for an example but, I presume it's not doable. I am looking to hopefully be proven wrong or to find an official confirmation that it's not doable.
Before using Hasura, I was doing transactional SQL queries that ensured that data was kept consistent.
For example, I would like to create a password reset token if a user requests it, only if the user can be found using an email address. Right now, I have to do 2 queries:
Try to find a user with the specified email address
Insert and assign the token to this user id
In that case, it's not too bad, but now if I want to consume that token, I have to do 3 queries:
Find the valid token
Change the password to the user associated with that token
Delete the token
Obviously, if something goes wrong and the token is not deleted, this could be an issue - so I would be curious to see if there would be ways to merge these queries/mutations into transactions.
Sounds like supporting nested updates would solve this problem for you with the least amount of effort. We are working on a rfc for the feature and hope to start development soon. Please follow this Github issue on our community for future updates.
https://github.com/hasura/graphql-engine/issues/1573
This comment outlines the current scope of the proposed feature. The rfc will provide a more complete explanation.
https://github.com/hasura/graphql-engine/issues/1573#issuecomment-1338057350
You can apply changes to rows that you filter by certain criteria. Here is a sample mutation:
mutation PasswordUpdate($id: uuid!, $token: String!, $new_password: String!) {
update_user(
where: {id: {_eq: $id}, token: {_eq: $token}}
_set: {token: null, password: $new_password}
) {
affected_rows
}
}
That query deletes the token and sets a password for all users (hopefully just one) that have the token assigned.
After some research here is what I found:
For the first example:
Try to find a user with the specified email address
Insert and assign the token to this user id
There are no solutions for this today and as answered by #damel, there is an ongoing RFC to support nested mutations: https://github.com/hasura/graphql-engine/issues/1573#issuecomment-1338057350
Hopefully, this feature will be out soon, but in the meantime, for most cases, it's not such a big deal to have multiple queries as it is possible to catch errors on the first query.
For the second example:
Find the valid token
Change the password to the user associated with that token
Delete the token
When sending multiple mutations in the same query, Hasura treats them as a transaction as announced in 2020.
Of course, it would be nice to do this in the same query (similar to the first example) but since there is a transaction on the mutation, for this case it's still not a problem.
I am sure there are probably cases where this can become a problem but I am not exposed to them right now. Nevertheless, it would be great if the RFC makes it to production, giving more options to Hasura users.
I follow stackoverflow for quite some time now. In most cases the problems I encountered were already mentioned and addressed by people before me. Now, I have an issue I have not found an applicable solution to yet. It may result from my humble understanding of the issue and not knowing what I actually am looking for, so I hope you can help me to at least better understand what happens. If additional info is required to make sense, please do not hesitate to ask.
Synopsis: One user of a program I built often (not always, interestingly) gets an InvalidAuthenticationToken error from the request python package when requesting calendar events with a token
generated by the msal package while none of the other users have any issues at all.
The situation is as follows:
I built a program for a small company which has to read out the events of some of its employees. I wrote it in python and used the msal and requests packages for the part of the interaction with MS Outlook:
import msal
import requests
class OutlookClient():
def __init__(self, client_id, authority):
# client_id and authority are the respective
# aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee style ids of the app I registered at azure.
self.app = msal.PublicClientApplication(
client_id = client_id,
client_credential = None,
authority = msal.authority.AuthorityBuilder(msal.authority.AZURE_PUBLIC,authority)
)
def getToken(self, username, pw):
# credentials of some dummy employee being authenticated to access
# the employees' calendars
self.auth = self.app.acquire_token_by_username_password(username,pw,
scopes=["Calendars.Read","Calendars.Read.Shared","People.Read"]
)
return
def getCalendar(self, agentCal, startDate, endDate):
# agentCal is the id of the employee in question obtained somewhere else.
graph_data = None
if 'access_token' in self.auth:
req = "https://graph.microsoft.com/v1.0/users/"+agentCal+"/calendar/calendarView"+\
"?startDateTime="+ startDate.strftime("%Y-%m-%dT02:00")+\
"&endDateTime="+ endDate.strftime("%Y-%m-%dT23:00")+\
graph_data = requests.get(req,
headers={'Authorization': 'Bearer ' + self.auth['access_token'], 'content-type': 'application/json'}
).json()
try:
return graph_data['value']
except KeyError:
return []
Currently, three employees are testing the program in the field. One of them faces a recurring error which neither of the other users nor I can reproduce. When getCalendar gets called the request gets answered as
graph_data =
{'error':
{'code': 'InvalidAuthenticationToken',
'message': 'Access token has expired or is not yet valid.',
'innerError':
{'date': '2022-10-27T05:56:39',
'request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'client-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
}
}
}
whereas all other users and the specific user also sometimes should get a list of events. The token, however, looked fine to me:
self.auth =
{'token_type': 'Bearer',
'scope': 'Calendars.Read Calendars.Read.Shared Calendars.ReadWrite Mail.ReadWrite Mail.Send openid People.Read profile User.Read email',
'expires_in': 4581,
'ext_expires_in': 4581,
'access_token': 'eyJ0eXAiOiJKV1Q...',
'refresh_token': '0.AREA...',
'id_token': 'eyJ0eXAiOiJKV1Q...',
'client_info': 'eyJ1aWQ...',
'id_token_claims': {...}
}
I have limited opportunity to identify the issue at the user's computer, unfortunately, as they are currently overwhelmed with work and therefore not very responsive. So, before I bother them and myself with many trial and error approaches I hoped you could share some ideas.
The problem persists, as I was told, even when the program is closed and restarted.
I let the program create a log-file which stores the relevant variables such as the token etc. to see if any pattern arises but everytime a token is generated independent of whether the request for the calendars is answered correctly or incorrectly.
I thought that maybe the program gets started and after some time the token expires but in the log-file it seems to still be valid.
Sorry, it was as expected and I initially just did not check the correct things.
Indeed, the token expired and I did not see it. One solution is to check whether a request gets answered properly and if not get a token by the refresh token
if 'error' in graph_data:
self.auth = self.app.acquire_token_by_refresh_token(\
self.auth['refresh_token'],scopes)
and request again.
I'm working with the SAFE stack (https://safe-stack.github.io/) and through the example dojo. It's great so far.
I'd like to extend the example to include a button to login/auth via Google. So I looked at an example on the Google website (https://developers.google.com/identity/sign-in/web/build-button). And then I had a look how to do authentication using ASP.NET (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.1&tabs=aspnetcore2x) As a result I ended up confused as to how to integrate this into a SAFE project. Can someone tell me what they would do? SHould I be trying to use ASP.NET Identity or should I be using the JWT approach? I don't even know if they are the same since I'm very new to web frameworks.....
The other question I have is how would one inject raw Javascript into the client side of a SAFE project. The google example above shows raw JS/CSS/HTML code? Should I be injecting that as is or should I look in React for some button that does this and map that idea back through Fable?
Setting up OAuth
The easiest way to use Google OAuth is to wait until the next release of Saturn, at which point Saturn will include the use_google_oauth feature that I just added. :-) See the source code if you're interested in how it works, though I'm afraid you can't implement this yourself with use_custom_oauth because you'll run into a type error (the underlying ASP.NET code has a GoogleOptions class, and use_custom_oauth wants an OAuthOptions class, and they aren't compatible).
To use it, add the following to your application CE:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" []
The last parameter should be a sequence of string * string pairs that represent keys and values: you could use a list of tuples, or a Map passed through Map.toSeq, or whatever. The keys of that sequence are keys in the JSON structure that Google returns for the "get more details about this person" API call, and the values are the claim types that those keys should be mapped to in ASP.NET's claims system. The default mapping that use_google_oauth already does is:
id → ClaimTypes.NameIdentifier
displayName → ClaimTypes.Name
emails[] (see note) → ClaimTypes.Email
Those three are automatically mapped by ASP.NET. I added a fourth mapping:
avatar.url → `"urn:google:avatar:url"
There's no standard ClaimTypes name for this one, so I picked an arbitrary URN. Caution: this feature hasn't been released yet, and it's possible (though unlikely) that this string might change between now and when the feature is released in the next version of Saturn.
With those four claim types mapped automatically, I found that I didn't need to specify any additional claims, so I left the final parameter to use_google_oauth as an empty list in my demo app. But if you want more (say you want to get the user's preferred language to use in your localization) then just add them to that list, e.g.:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" ["language", "urn:google:language"]
And then once someone has logged in, look in the User.Claims seq for a claim of type "urn:google:language".
Note re: the emails[] list in the JSON: I haven't tested this with a Google account that has multiple emails, so I don't know how ASP.NET picks an email to put in the ClaimTypes.Email claim. It might just pick the first email in the list, or it might pick the one with a type of account; I just don't know. Some experimentation might be needed.
Also note that third-party OAuth, including GitHub and Google, has been split into a new Saturn.Extensions.Authorization package. It will be released on NuGet at the same time that Saturn's next version (probably 0.7.0) is released.
Making the button
Once you have the use_google_oauth call in your application, create something like the following:
let googleUserIdForRmunn = "106310971773596475579"
let matchUpUsers : HttpHandler = fun next ctx ->
// A real implementation would match up user identities with something stored in a database, not hardcoded in Users.fs like this example
let isRmunn =
ctx.User.Claims |> Seq.exists (fun claim ->
claim.Issuer = "Google" && claim.Type = ClaimTypes.NameIdentifier && claim.Value = googleUserIdForRmunn)
if isRmunn then
printfn "User rmunn is an admin of this demo app, adding admin role to user claims"
ctx.User.AddIdentity(new ClaimsIdentity([Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "MyApplication")]))
next ctx
let loggedIn = pipeline {
requires_authentication (Giraffe.Auth.challenge "Google")
plug matchUpUsers
}
let isAdmin = pipeline {
plug loggedIn
requires_role "Admin" (RequestErrors.forbidden (text "Must be admin"))
}
And now in your scope (NOTE: "scope" will probably be renamed to "router" in Saturn 0.7.0), do something like this:
let loggedInView = scope {
pipe_through loggedIn
get "/" (htmlView Index.layout)
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
get "/admin" (isAdmin >=> htmlView AdminPage.layout)
}
And finally, let your main router have a URL that passes things to the loggedInView router:
let browserRouter = scope {
not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage
pipe_through browser //Use the default browser pipeline
forward "" defaultView //Use the default view
forward "/members-only" loggedInView
}
Then your login button can just go to the /members-only route and you'll be fine.
Note that if you want multiple OAuth buttons (Google, GitHub, Facebook, etc) you'll probably need to tweak that a bit, but this answer is long enough already. When you get to the point of wanting multiple OAuth buttons, go ahead and ask another question.
MVC 2FA sometimes generates the same OTP (I have set to 6 numericals) and when you generate multiple OTPs, one can use the previous OTP.
Is there a way to generate unique OTPs and disable the previous generated OTP?
string code = await UserManager.GenerateTwoFactorTokenAsync(user.Id, provider);
This is the time set before the OTP expires
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(2));
I just stumbled across this post today and found a simple solution to the problem.
https://www.stevejgordon.co.uk/asp-net-core-identity-token-providers
This link describes the fact that the SecurityTokenStamp is used in the verification process. So for me it was a simple matter of updating it each time I sent out an SMS token for the user. Effectively invalidating the original one.
if(await userManager.UpdateSecurityStampAsync(user.Id) != IdentityResult.Success)
{
// https://www.stevejgordon.co.uk/asp-net-core-identity-token-providers
// we update it to effectively reset all the token validation stuff
return IdentityResult.Failed("failed to update the security stamp");
}
// Send token - this may throw but thats ok as we just rollback
string code = await userManager.GenerateTwoFactorTokenAsync(user.Id, "twilio");
await userManager.SmsService.SendAsync(new Microsoft.AspNet.Identity.IdentityMessage
{
Destination = user.UserName,
Body = "Your security code is: " + code
});
The OTP which is generating via UserManager.GenerateTwoFactorTokenAsync is not a One Time Password even though we called it OTP. It is a TOTP ( Time based one time password).
https://en.wikipedia.org/wiki/Time-based_One-Time_Password
Therefore in a particular time period the generated passwords can be slimier.
For SMS and Email I have noticed that the time period is around 90 seconds. That means within 90 seconds it generates the same password.
For authenticator app also there is this default time period.
By doing lot of researches around this, What I have noticed is, to change this default time period we need to create a custom 2FA token provider.
OTP are time based and not recorded anywhere. If you generate 2 OTPs within a short period of time, you'll get identical strings. And this is how algorithm is working and there is no easy way around it.
First of all, I'm not a security expert and new to form validation, password storing and wp plugin development. Any wp plugin tutorial I've been looking at never had a chapter about API passwords. Googling for wordpress password issues didn't return any satisfying results. So that's why I'm asking here.
I want to create a custom Wordpress Plugin which works with a Soap API of another page. To use the Soap API a login is needed. So the Wordpress built in functions add_option(), update_option() and get_option() are all working with plain text.
But I know that in the wp_config file authentication keys can be saved. But how to use them in an option page form to encrypt the password? And would it be possible just to store them in the database, decrypt it and use it in the backend but not showing it on the options page if the user visits that page again. So that the password field just has some black spots in it (not the same amount of the chars of the pass) and the password option only is updated if something is written into that field.
Normally the code is like this:
register_setting( 'my_plugins_option', 'my_options', 'my_validation_function' );
$options = array(
'user' = > 'name',
'password' = > 'pass',
//... other options
)
update_option( 'my_plugins_option', $options );
But how could I make this more secure? I've seen many plugin examples but nothing was about storing passwords. I'm looking for something like this:
function my_validation_function($input){
if($input['password']=='•••••'){
//use the default value of the database if nothing was changed
$old_options=get_option('my_plugins_option');
$input['password']=some_decrypting_function($old_options['password']);
}
else{
//use the password sent from the form
$password=esc_sql(some_encrypting_function($input['password']));
}
// ... validate the other inputs
update_option( 'my_plugins_soap_api_pass', $password );
}
P.S.: This code is not tested yet of course because I don't know how to work with passwords in wordpress plugins and so I wanted to ask for the best practices first.
The other question is: If the modified version of the code from above would work and the password is saved once and never loaded into the Dashboard frontend again (just showing this: '•••••' once typed in) would it be save to work with the get_option() function, decrypt the password and use it in the backend?
Here are a couple recommendations. They aren't specific to WP, but can apply.
1) Save an encrypted password in the options table (use whatever encrypting function you want, just don't write your own)
2) In the options page, simply do NOT output the password. Leave that field blank, and don't require it to be entered if there is already a password stored in the database.
3) Only decrypt the password retrieved from the options table just prior to actually needing it in code.