Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch Azure AD Profile Image and set it as the Umbraco Avatar #11

Open
dalbard opened this issue Jun 27, 2023 · 5 comments
Open

Fetch Azure AD Profile Image and set it as the Umbraco Avatar #11

dalbard opened this issue Jun 27, 2023 · 5 comments

Comments

@dalbard
Copy link

dalbard commented Jun 27, 2023

As the title suggests, how would you go about modifying the code to fetch the logged in user's Azure AD profile image and set it as their Umbraco Avatar?

@stevetemple
Copy link
Member

Interesting, I'll take a look and see if that would be possible with the data the claims supplies

@mistyn8
Copy link
Contributor

mistyn8 commented Jul 18, 2023

I had a little look.. doesn't seem to be anything from the claims that can be used, but did find two aproaches.

restapi call.. but I can't seem to get an access_token from the externalLogin, and nothing is being stored in the umbracoExternalLoginToken table?
I've added options.SaveTokens = true; and that gives me an id_token but doesn't seem to let me use that against the graphapi

string? accessToken = loginInfo.AuthenticationTokens?.FirstOrDefault(t => t.Name.Equals("id_token"))?.Value;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var pictureResult = httpClient.GetAsync("https://graph.microsoft.com/v1.0/me/photo/$value").Result;

Then stumbled on maybe using MicrosoftGraph package by adding .AddMicrosoftGraph() after EnableTokenAcquisitionToCallDownstreamApi(..) and changing scopes to var initialScopes = new string[] { "user.read" };

but then get issues with Cannot consume scoped service 'Microsoft.Graph.GraphServiceClient' from singleton 'Microsoft.Extensions.Options.IOptionsMonitor'

using (var photoStream = await _graphServiceClient.Me.Photo.Content.Request().GetAsync())
{
    byte[] photoByte = ((MemoryStream)photoStream).ToArray();
}

beyond my skills at this point. :-)

@mistyn8
Copy link
Contributor

mistyn8 commented Jul 18, 2023

So turned out just had to update Microsoft.Identity.Web latest is 2.13.0 and then add options.SaveTokens = true to the AddMicrosoftIdentityWebApp config
And heh presto access_token is now present.
and I can fetch the picture from the msgraph api
image

@mistyn8
Copy link
Contributor

mistyn8 commented Jul 18, 2023

Final piece to the puzzle, though not sure .Result for that GetAsync is correct.. though didn't get far with async await up the method tree before method signatures started complaining.

private readonly AzureSsoSettings _settings;
private readonly IUserService _userService;
private readonly MediaFileManager _mediaFileManager;

public MicrosoftAccountBackOfficeExternalLoginProviderOptions(AzureSsoSettings settings, IUserService userService, MediaFileManager mediaFileManager)
{
    _settings = settings;
    _userService = userService;
    _mediaFileManager = mediaFileManager;
}
if (loginInfo.AuthenticationTokens?.FirstOrDefault(t => t.Name.Equals("access_token"))?.Value is string accessToken)
{
    using (var httpClient = new HttpClient())
    {
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var pictureResult = httpClient.GetAsync("https://graph.microsoft.com/v1.0/me/photo/$value").Result;

        if (pictureResult.IsSuccessStatusCode)
        {
            if (_userService.GetByUsername(user.UserName) is User u && pictureResult.Headers.ETag is EntityTagHeaderValue etag)
            {
                //etag : identifier for a specific version of a resource
                u.Avatar = $"UserAvatars/{etag.ToString().GenerateHash<SHA1>()}.jpg";

                if (u.IsDirty())
                {
                    using (Stream fs = pictureResult.Content.ReadAsStream())
                    {
                        _mediaFileManager.FileSystem.AddFile(u.Avatar, fs, true);
                    }

                    _userService.Save(u);
                }
            }
        }
    }
}

UserAvatars in the core here.. https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs#L185-L196C1

@dalbard
Copy link
Author

dalbard commented Jul 28, 2023

Final piece to the puzzle, though not sure .Result for that GetAsync is correct.. though didn't get far with async await up the method tree before method signatures started complaining.

private readonly AzureSsoSettings _settings;
private readonly IUserService _userService;
private readonly MediaFileManager _mediaFileManager;

public MicrosoftAccountBackOfficeExternalLoginProviderOptions(AzureSsoSettings settings, IUserService userService, MediaFileManager mediaFileManager)
{
    _settings = settings;
    _userService = userService;
    _mediaFileManager = mediaFileManager;
}
if (loginInfo.AuthenticationTokens?.FirstOrDefault(t => t.Name.Equals("access_token"))?.Value is string accessToken)
{
    using (var httpClient = new HttpClient())
    {
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var pictureResult = httpClient.GetAsync("https://graph.microsoft.com/v1.0/me/photo/$value").Result;

        if (pictureResult.IsSuccessStatusCode)
        {
            if (_userService.GetByUsername(user.UserName) is User u && pictureResult.Headers.ETag is EntityTagHeaderValue etag)
            {
                //etag : identifier for a specific version of a resource
                u.Avatar = $"UserAvatars/{etag.ToString().GenerateHash<SHA1>()}.jpg";

                if (u.IsDirty())
                {
                    using (Stream fs = pictureResult.Content.ReadAsStream())
                    {
                        _mediaFileManager.FileSystem.AddFile(u.Avatar, fs, true);
                    }

                    _userService.Save(u);
                }
            }
        }
    }
}

UserAvatars in the core here.. https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs#L185-L196C1

Great findings! Does this update your avatar? I've just tested the code but it doesn't seem to work for me... maybe I'm not calling the function properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants