ADFS: Send user manager's email address as a claim - adfs

I integrated SSO with ADFS, and I need to get the manager's email of whoever is currently logging in. When I send over the manager attribute using:
query = ";manager;{0}"
I only get back the manager name and org.
I tried following this article:
https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/ff678048(v=ws.10)
However, the rules given in the example result in a syntax error.
and tried various methods from threads such as:
https://social.msdn.microsoft.com/Forums/expression/en-US/903e217b-a441-41d6-9400-661644820500/extract-manager-email-address?forum=Geneva
I think I need to query for the manager using their name somehow, but I am having trouble coming up with a solution. I do understand I likely need multiple rules. Can anyone shed some light on this problem?
Thank you

The following rule illustrates how you can issue a mail attribute as a “ManagerEmail” claim:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
&& c1:[Type == "http://schemas.xmlsoap.org/claims/ManagerDistinguishedName"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/ManagerEmail"), query = "(&(distinguishedName={0})(objectClass=user));mail;{1}", param = c1.Value,
param = regexreplace(c1.Value, ".*DC=(?<domain>.+),DC=corp,DC=yourdomain,DC=com", "${domain}\username"));
Make sure that the user’s manager is in the same domain as the user, i.e. corp.yourdomain.com in the above example, and your test user does have some email specified.

Related

Request.auth.metadata in security rules?

I have a Firebase project where I'd like for users to be able to see when other users created their profiles. My initial hope was that I could use "user.metadata.creationTime" on the frontend to pass the date into the user's extra info document and verify that it is correct by having "request.resource.data.datecreated == request.auth.metadata.creationTime" as a Database Rule, but it looks like it is not possible according to the documentation.
Is there any way I can verify that the creation date is correct on the backend?
More info edit: Below is the code that is being triggered when a user creates a new account on my profile. The three values are displayed publicly. I'm creating a niche gear for sale page so being able to see when a user first created their account could be helpful when deciding if a seller is sketchy. I don't want someone to be able to make it seem like they have been around for longer than they have been.
db.collection('users').doc(user.uid).set({
username: "Username-156135",
bio: "Add a bio",
created: user.metadata.creationTime
});
Firestore rules:
match /users/{id} {
allow get;
allow create, update: if request.resource.data.username is string &&
request.resource.data.bio is string &&
request.resource.data.created == request.auth.metadata.creationTime;
}
user.metadata.creationTime, according to the API documentation is a string with no documented format. I suggest not using it. In fact, what you're trying to do seems impossible since that value isn't available in the API documentation for request.auth.
What I suggest you do instead is use a Firebase Auth onCreate trigger with Cloud Functions to automatically create that document with the current time as a proper timestamp. Then, in security rules, I wouldn't even give the user the ability to change that field, so you can be sure it was only ever set accurately by the trigger. You might be interested in this solution overall.

Adding a button for google signin using f#/fable/asp.net/react

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.

Telegram bot: How to mention user by its id (not its username)

I am creating a telegram bot and using sendMessage method to send the messages.
it is easy to mention user using #username, But how to mention user when they don't have username?
If using the telegram app/web, we can mentioned the user by #integer_id (name), and telegram app/web will convert it into clickable text. integer_id will be generated automatically when we select the user, after typing #.
another background:
I am trying to use forceReply and I want to target user, if they have username, I can easily target them, by mentioning them on the text on sendMessage method.
the bot I am creating is a "quiz" like bot. where each player need to take turn, and the bot is sending them the question, each msg from bot will target different player.
NOTE: I am not disabling the Privacy Mode, I don't want telegram bombing my server with msg I don't need. it was overloading my cheap nasty server. so, disabling it not an option.
I am open for other solution, where the bot can listen to selected player.
thanks.
UPDATE 21/10:
I've spoke to BotSupport for telegram, they said, for now Bots can't mention user without username.
so in my case, I still keep using forceReply, and also, gave a short msg to user which doesn't have username to set it up, so they can get the benefit from forceReply function.
According to official documentation it is possible to mention user by its numerical id with markup:
[inline mention of a user](tg://user?id=123456789)
According to this link :
it is possible to mention user by its numerical id with markup:
Markdown style
To use this mode, pass Markdown in the parse_mode field
when using sendMessage. Use the following syntax in your message:
[inline mention of a user](tg://user?id=123456789)
and you can also use HTML style :
HTML style
To use this mode, pass HTML in the parse_mode field when using sendMessage. The following tags are currently supported:
inline mention of a user
Try this:
#bot.message_handler(func=lambda message: True)
def echo_message(message):
cid = message.chat.id
message_text = message.text
user_id = message.from_user.id
user_name = message.from_user.first_name
mention = "["+user_name+"](tg://user?id="+str(user_id)+")"
bot_msg = f"Hi, {mention}"
if message_text.lower() == "hi":
bot.send_message(cid, bot_msg, parse_mode="Markdown")
For python-telegram-bot you can do the following:
user_id = update.message.from_user['id']
user_name = update.message.from_user['username']
mention = "["+user_name+"](tg://user?id="+str(user_id)+")"
response = f"Hi, {mention}"
context.bot.sendMessage(chat_id=update.message.chat_id,text=response,parse_mode="Markdown")
No, this restriction is related to Telegram's privacy policy and prevention of abuse.
It is possible to mention a user when sending messages (BOT API), but that is not what you need:
[inline mention of a user](tg://user?id=<user_id>)
Links tg://user?id= can be used to mention a user by their id without using a username. Please note:
These links will work only if they are used inside an inline link. For example, they will not work, when used in an inline keyboard button or in a message text.
These mentions are only guaranteed to work if the user has contacted the bot in the past, has sent a callback query to the bot via inline button or is a member in the group where he was mentioned.
https://core.telegram.org/bots/api#markdown-style
you need to link to the text: "tg://user?id=" and id
user_id = 123456XX # id of the user to mention
chat_id = 123456XXX # chat id where to mention
user_name = name of user
await bot.send_message(chat_id, f"<a href='tg://user?id={user_id}'>{user_name}</a>", "HTML")
here is an example:
#dp.message_handler()
async def mention(msg: types.Message):
await msg.answer(f"<a href='tg://user?id={msg.from_user.id}'>{msg.from_user.full_name}</a>", "HTML")
Bots are able to tag users by their ID, they just can't do this using the official HTTP Bot API.
Update: Not necessairy anymore, since Telegram added native Support for this.
If you log into your bots account with MadelineProto (PHP) you can use this 'link' to mention someone by it's ID with parse_mode set to markdown
[Daniil Gentili](mention:#danogentili)

Creating a user failed. Is there a way to interogate the error?

I am using the ASP.NET Identity framework, with the EntityFramework provider (IdentityDbContext).
I attempt to create a user by calling UserManager.CreateAsync(..).
It returns an object of type IdentityResult, with the following values:
{
Succeeded: false,
Errors: ["Name AndrewShepherd is already taken."]
}
The error is valid - there is indeed another user called "AndrewShepherd" in the database. This is not a name the user picked; instead I am generating this name from the Outh2 account information provided by their Google account. (Google Accounts don't enforce unique names, just unique email addresses).
Since the problem is a duplicate name, I can simply try appending a number to the name and trying again. I would programmatically attempt to create AndrewShepherd_1, AndrewShepherd_2, AndrewShepherd_3 until I am either successful or get a different error.
But how do I programatically determine that the problem was a duplicate name?
Options are:
Perform a pattern match on the error string. This solution is guaranteed to break when the next version of ASP.NET Identity has a differently worded error messages or we internationalize the website.
Run the check before creating the user. I would call UserStore.FindByNameAsync to determine if the name had already been taken before invoking UserManager.CreateASync(..). This would have a small concurrency risk if two different sessions are attempting to add the same user name at the same time.
It would be some much easier if we could simply perform a check like this:
if(identityResult.Errors.Where(e => e.ErrorCode == IdentityErrors.DuplicateName).Any())
Is there a way I can perform a unique user check then add a user, that's safe in a concurrent environment?
As an alternaive, you can check if the user name is exist in your DB like that. If user name is already taken, you can change new user name like AndrewShepherd_1, AndrewShepherd_2, AndrewShepherd_3.
using (YourProjectName.Models.DefaultConnection db = new Models.DefaultConnection ())
{
if (db.AspNetUsers.Select(q => q.UserName).Contains("usernameThatYouWantToCheck"))
{
/*Your code*/
}
}

Registering new users via OAuth2 : what to set as user identifier for future log ins?

I have managed to successfully configure this. The problem is, when I change the lines below :
//I have set all requested data with the user's username
//modify here with relevant data
$user->setUsername($username);
$user->setEmail($username);
$user->setPassword($username);
into the information I want to retrive, such as real name, email, my generated password etc, when I click the Login button for Facebook per say, I am asked again if I want to connect with my local testing site.
From what I understand, in the documentation I linked above, this :
$user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
is the line that checks if the user exists or not, and the initial code by itself, sets either facebook_id or twitter_id (this is how I save them) as a new User *username*. If I change the line
$user->setUsername($username); //same as facebook/twitter _id
into
$user->setUsername(setProperUsername()); //sets a proper unique username
Then everytime I try to login I get the "Register" message. So, I have a general idea of how it works but I am having a hard time understanding some things:
1. When I have registered with Facebook and I login with twitter, I register again, no knew row is created, but missing twitter_id fields are updated/populated, username stays intact. How come HWI/FOSUB knows I am the same person when my previous data were from Facebook not Twitter?
2. If there is a global way of knowing I am the same person, what data from the $response object should I use as a key to identify already registered users?
After testing a lot with this, I have the answer if anyone runs into this type of situation
Check your default_target path, make it so it is /profile, /account etc, don't default to login again. Also, if a user is already logged in, do not make him access your login page. This was why my data was being updated. I was basically logged in with my Facebook account and registering with my Twitter account too.
No, there is no global way of knowing I am the same person. The $response object sent me a unique ID for that specific user according to the provider policy. You might use this to identify already registered users and log them in.

Resources