Delay when adding user - azure-active-directory

I am adding a user to AAD and following adding the user to a group. However it is like there is a small delay before the user us added. Is the expected behavior? If yes how long can you expect this delay?
In the code below adding the user to the group fails because user is null. After some seconds however I can add it. To implement this I need to know what the expected delay is. Can it be minutes or hours?
var email = "xxx";
var graphClient = await Graph.GraphServiceClientExtension.GetGraphClient( _config.AzureAppIdExternal, _config.AzureAppSecretExternal, _config.TenantNameExternal);
var invite = new Microsoft.Graph.Invitation();
invite.InvitedUserEmailAddress = email;
invite.InvitedUserDisplayName = email;
invite.InviteRedirectUrl = "xxx";
invite.SendInvitationMessage = true;
var inviteMsgInfo = new Microsoft.Graph.InvitedUserMessageInfo();
inviteMsgInfo.CustomizedMessageBody = "Velkommen, xx";
invite.InvitedUserMessageInfo = inviteMsgInfo;
var inviteUserResponse = await graphClient.Invitations.Request().AddAsync(invite);
var user = (await graphClient.Users.Request().Filter($"mail eq '{email}'").GetAsync()).FirstOrDefault();
await graphClient.Groups["xx-7b17-4ed7-9b75-xx"].Members.References.Request().AddAsync(user);

It can be confirmed that there is a delay when adding user with Azure. It is always 5-10mins, but I couldn't make sure the time of delay.
It seems better to check if the user has been added, then you could add it into groups. You can use the Graph API and query for the user you want, like this:
https://graph.microsoft.com/v1.0/users?$filter=startswith(userPrincipalName,'someuser#mytenant.onmicrosoft.com')
This is a code sample for you to refer.

Related

Graph Active Directory Users API returning lots of duplicates

I'm using the Graph API in an azure function to get all users from active directory.
The code I am using shown below but pseudocode is
if there is no previous deltaLink, get all users, else use the deltalink.
Get the first page.
While the userPage.NextPageRequest is not null, get the next page.
save this page to a database.
first page of 200 users returns as expected.
After this I expect each subsequent page will have its limit of 200 users, until the last page but this is not what I am seeing.
Very often the next page has less than 200 users, 187 to be exact and they are mostly duplicates of what I got in the first page.
Am I doing something incorrect here to cause this behavior?
var scopes = new[] { "https://graph.microsoft.com/.default" };
IUserDeltaCollectionPage usersPage;
UserDeltaCollectionPage lastDeltaPage = new UserDeltaCollectionPage();
if (deltaLink == null)//get all users, there is no delta link from a previous full load.
{
usersPage = await graphClient
.Users
.Delta()
.Request()
.GetAsync();
SaveUsersToDatabase(usersPage, sqlConnString, "insert");
}
else//use delta query to look for updates since last load.
{
lastDeltaPage.InitializeNextPageRequest(graphClient, deltaLink.ToString());
usersPage = await lastDeltaPage.NextPageRequest.GetAsync();
SaveUsersToDatabase(usersPage, sqlConnString, "insert");
}
while (usersPage.NextPageRequest != null)
{
usersPage = await usersPage.NextPageRequest.GetAsync();
SaveUsersToDatabase(usersPage, sqlConnString, "insert");
}
if (usersPage.NextPageRequest == null)//get delta link if this is the last page
{
usersPage.AdditionalData.TryGetValue("#odata.deltaLink", out newDeltaLink);
}
FYI - SaveUsersToDatabase just serializes the usersPage to json and sends it to a database.
Regarding user count, you can check at Azure AD for the count of users and may be duplicates are there itself. Another way to check that is to pull the data by providing Top to validate the count
usersPage = await graphClient
.Users
.Delta()
.Request()
.Top(400)
.GetAsync();
However, I would suggest you to use PageIterator to iterate over all the users as shown below in that case, you don't need to check the nextLink.
List<User> userResult = new List<User>();
users = await graphClient
.Users
.Delta()
.Request()
.GetAsync();
var userIterator = PageIterator<User>
.CreatePageIterator(graphClient, users, user) =>
{
userResult.Add(users);
return true;
});
//get all users
await userIterator.IterateAsync();
Troubleshooting shows that only the delta query is returning the duplicates, and is also returning the same ones each time.
We are going to avoid delta query for the moment and may come back to it in future.
thanks for the advice all.

How to get GuildMember data from a User through a mention

I am making a "user info" command that returns the user's Discord username, their ID, their server join date and whether or not they are online. I am able to display all the information through user.id, user.username, and user.presence.status. But when I try to use user.joinedAt I get undefined in the display.
I know this is because the User class and the GuildMember class are not the same, and that the GuildMember class contains a User object.
But my problem is: how I could get the .joinedAt data from my user mention?
Here is my current code:
let user = message.mentions.users.first();
let embed = new Discord.RichEmbed()
.setColor('#4286f4')
.addField("Full Username:", `${user.username}#${user.discriminator}`)
.addField("User ID:", `${user.id}`)
.addField("Server Join Date:", `${user.joinedAt}`)
.addField("Online Status:", `${user.presence.status}`)
.setThumbnail(user.avatarURL);
message.channel.send(embed);
Here's the code for my user info command:
if (msg.split(" ")[0] === prefix + "userinfo") {
//ex `member #Rinkky
let args = msg.split(" ").slice(1) // gets rid of the command
let rMember = message.guild.member(message.mentions.users.first() || message.guild.members.get(args[0])) // Takes the user mentioned, or the ID of a user
let micon = rMember.displayAvatarURL // Gets their Avatar
if(!rMember)
return message.reply("Who that user? I dunno him.") // if there is no user mentioned, or provided, it will say this
let memberembed = new Discord.RichEmbed()
.setDescription("__**Member Information**__")
.setColor(0x15f153)
.setThumbnail(micon) // Their icon
.addField("Name", `${rMember.username}#${rMember.discriminator}`) // Their name, I use a different way, this should work
.addField("ID", rMember.id) // Their ID
.addField("Joined at", rMember.joinedAt) // When they joined
await message.channel.send(memberembed)
};
This will send an embed of their user info, this is my current code and
rMember.joinedAt
does work for me.
edit:
After looking at your question again, I found out I didn't need to post everything, you can't get the joined at because it's just the mention. Try this:
let user = message.guild.member(message.mentions.users.first())
Should work
You can technically find it by fetching getting the member from the guild and then using GuildMember.joinedAt: since the User class represents the user in every guild, you will always need the GuildMember to get info about a specific guild.
let user = message.mentions.users.first(),
member;
if (user) member = message.guild.member(user);
if (member) embed.addField("Server Join Date:", `${member.joinedAt}`);
With this said, I would not suggest you to do that, since it's not really efficient. Just take the mention from the members' collection and then take the user from that.
let member = message.mentions.members.first(),
user;
if (member) user = member.user;
The downside of this is that you can't use it if you want your command to be executable from the DMs too. In that case you should use the first method.
const member = message.channel.guild.members.cache.find(member => member.user == message.mentions.users.first())

How can I refresh an expired access token when it’s stored in a claim in a cookie?

Using Identity Server 4 server.
In client’s Startup.Auth.cs :
private static void ConfigureAuth(IAppBuilder app)
{
ISettingsReader settingsReader = Services.Resolve<ISettingsReader>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
ExpireTimeSpan = new TimeSpan(1, 0, 0),
CookieSecure = CookieSecureOption.Always,
CookieHttpOnly = true,
SlidingExpiration = true
});
var platformUri = settingsReader.GetSetting("PlatformUri")?.TrimEnd('/');
var platformApiKey = settingsReader.GetSetting("PlatformApiKey");
var deploymentURL = settingsReader.GetSetting("deploymentURL")?.TrimEnd('/');
var authority = $"{platformUri}/identity";
string clientSecret;
string clientId = SplitApiKey(platformApiKey, out clientSecret);
var options = new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
Authority = authority,
RedirectUri = $"{deploymentURL}/signin/callback",
ResponseType = "id_token token",
Scope = "platform openid",
UseTokenLifetime = false,
SignInAsAuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = SecurityTokenValidatedHandler,
RedirectToIdentityProvider = RedirectToIdentityProviderHandler,
}
};
app.UseOpenIdConnectAuthentication(options);
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
}
Note – the cookie’s expire timespan has been set to 5 mins for debugging, normally would be set to an hour.
And then stash the access_token in the validated handler (as per several articles) so that we can use it later for api calls:
private static async Task SecurityTokenValidatedHandler(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var jwtDetails = JsonWebToken.Parse(notification.ProtocolMessage.AccessToken);
notification.AuthenticationTicket.Identity.AddClaims(
jwtDetails.Claims.Where(c => DesiredAccessTokenClaims.Contains(c.Type)));
notification.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", notification.ProtocolMessage.AccessToken));
notification.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
}
This works fine. However, although the cookies do auto refresh themselves, with sliding expiration (typically after 2-3 minutes, rather than the full 5), there doesn’t seem to be a way to refresh the access token which is held in the claim, so although the user will remain logged in, the access token will be useless after it expires.
Is this the right way to be going about this? And if so, is there a way to update the access token in the claim, in the background without disturbing the user? It seems that the ideal solution would be to have the cookie refresh also trigger the SecurityTokenValidatedHandler so that a renewed claim can be added into the new cookie, although despite looking in CookieManager, etc. there doesn't seem to be an event which is triggered when the cookie sliding refreshes itself. Does anyone know a way to do this?
Many thanks for your time!
If anyone comes across this issue, the answer was to change from implicit to hybrid. Hybrid allows refresh tokens, whereas implicit, not so much. In our case we needed Hybrid and Client Creds

Random access denied errors on User Extensions

When using extensions in the Graph API:
graphUser = graphClient.Users.Request().AddAsync(graphUser).Result;
OpenTypeExtension newExtension = new OpenTypeExtension()
{
ExtensionName = "CustomName",
AdditionalData = new Dictionary<string, object>
{ { "CustomID", user.CustomID }
}
};
graphClient.Users[graphUser.UserPrincipalName]
.Extensions
.Request()
.AddAsync(newExtension)
.Wait();
I randomly get these errors:
Code: AccessDenied
Message: Access Denied
Sometimes it works, sometimes it doesn't. I can't seem to find a correlation.
When I step trough the code in the debugger it works more often then if I run it without interruption. But if I add a sleep between the lines (to account for processing delay), it doesn't fix the issue.
The application has all the required rights to access the API.
The issue isn't solely in the POST, but also on the GET as illustrated in the code sample below which results in the same error.
User user = graphClient.Users[userName]
.Request()
.GetAsync()
.Result;
user.Extensions = graphClient.Users[userName]
.Extensions
.Request()
.GetAsync()
.Result;
Does anyone have experience with this issue? Thanks in advance!
EDIT:
I figured out that once the errors start showing, the user needs to be deleted. Errors on one user don't necessarily mean errors on another user.
EDIT 2:
I also posted this as an issue on GitHub. Here you can find more information about this problem. It's now labeled as a bug.
It turns out that the User Principal Name is a cached reference.
Since I was running tests, meaning recreating the same test user a lot, the reference UPN was pointing to the old user object resulting in the Access Denied errors.
The issue can be avoided by using the Id of the object, like this:
graphClient.Users[graphUser.Id]
.Extensions
.Request()
.AddAsync(newExtension)
.Wait();
I believe the team is going to fix the reference bug, but I can't speak for them of course. In either case I would recommend using the Id attribute to be sure.
Based on the test, when we create a new user in Azure Active Directory, it seems that there is some delay we can operate for that user. Even the user is returned successfully when I using the Graph to filter the user, it still may failed when I add the extension to that user.
For this issue, I added a line of addition code to make the current thread sleep and then it works.
var userPrincipalName = "userPrincipalName8#adfei.onmicrosoft.com";
var graphUser = new User() { AccountEnabled = true, MailNickname = "MailNickname", UserPrincipalName = userPrincipalName, DisplayName = "userPrincipalName", PasswordProfile = new PasswordProfile() { Password = "islkdifde123!", ForceChangePasswordNextSignIn = false } };
graphUser = graphClient.Users.Request().AddAsync(graphUser).Result;
OpenTypeExtension newExtension = new OpenTypeExtension()
{
ExtensionName = "CustomName",
AdditionalData = new Dictionary<string, object>
{
{ "CustomID", "abc" }
}
};
Thread.Sleep(4000);
graphClient.Users[graphUser.UserPrincipalName]
.Extensions
.Request()
.AddAsync(newExtension)
.Wait();
However the detailed error message for this issue should be code=ResourceNotFound,message=User not found. Please check whether the error is same and this workaround is helpful.

WebSecurity.GetUserID(HttpContext.Current.User.Identity.Name) does not always return the UserID

I have a WebMatrix application and I am using WebSecurity.GetUserId(Http.Current.User.Identity.Name) to grab the UserID. Sometimes it returns the UserId and then other times it returns a -1. I have a login page therefore, it should be getting the UserId of the person logged into the application.
Here is my code:
var UserId = WebSecurity.GetUserId(HttpContext.Current.User.Identity.Name);
var User = WebSecurity.CurrentUserName;
var UserEmail = WebSecurity.CurrentUserName;
var dbApp = Database.Open("ApplicationServices");
var selectUser = "SELECT UserId, UserName, Location FROM LocationTable, UserTable WHERE (UserId) = (#0) AND (UserLocation) = (Id)";
var person = dbApp.QuerySingle(selectUser, UserId);
Why isn't it consistent?
Chances are you (or your users) are merely experiencing a session timeout (i.e. WebSecurity.CurrentUserId = -1) and in that case, you'd want to redirect the users back to your login page once they have timed out. You'll need to determine if you are using forms authentication, windows authentication or (ick) server sessions in order to determine/set the timeout value, and test as necessary.
The best thing for your users is to use the AuthorizeAttribute/AuthorizeCore and it's HandleUnauthorizedRequest Method to redirect them back to your login page on session expiration.

Resources