Codeunit 9011 Azure AD Graph User Impl.
- App
- System Application
- Namespace
- System.Azure.Identity
- Versions
- 17-28
Versions171819202122232425262728
Source in 29
src/System Application/App/Azure AD User/src/AzureADGraphUserImpl.Codeunit.al391 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace System.Azure.Identity;
using System;
using System.Globalization;
using System.Security.AccessControl;
codeunit 9011 "Azure AD Graph User Impl."
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;
Permissions = tabledata User = rm,
tabledata "User Property" = r;
var
AzureADGraph: Codeunit "Azure AD Graph";
GraphUserInfo: DotNet UserInfo;
UserDoesNotObjectIdSetErr: Label 'The user with the security ID %1 does not have a valid object ID in Microsoft Entra.', Comment = '%1 = The specified User Security ID';
CouldNotFindGraphUserErr: Label 'A Microsoft Entra user with the object ID %1 was not found.', Comment = '%1 = The specified object id';
[TryFunction]
[NonDebuggable]
procedure GetGraphUser(UserSecurityId: Guid; ForceFetchFromGraph: Boolean; var GraphUserOut: DotNet UserInfo)
begin
InitializeGraphUser(UserSecurityId, ForceFetchFromGraph);
GraphUserOut := GraphUserInfo;
end;
[NonDebuggable]
procedure GetObjectId(UserSecurityID: Guid): Text
begin
InitializeGraphUser(UserSecurityID, false);
if IsNull(GraphUserInfo) then
exit;
exit(GraphUserInfo.ObjectId());
end;
[NonDebuggable]
procedure GetUserAuthenticationObjectId(UserSecurityId: Guid): Text
var
UserProperty: Record "User Property";
begin
if not UserProperty.Get(UserSecurityId) then
Error(UserDoesNotObjectIdSetErr, UserSecurityId);
exit(UserProperty."Authentication Object ID");
end;
[NonDebuggable]
procedure TryGetUserAuthenticationObjectId(UserSecurityId: Guid; var AuthenticationObjectId: Text): Boolean
var
UserProperty: Record "User Property";
begin
if not UserProperty.Get(UserSecurityId) then
exit(false);
AuthenticationObjectId := UserProperty."Authentication Object ID";
exit(true);
end;
[NonDebuggable]
procedure GetUser(AuthenticationObjectID: Text; var User: Record User): Boolean
var
UserProperty: Record "User Property";
begin
UserProperty.SetRange("Authentication Object ID", AuthenticationObjectID);
if UserProperty.FindFirst() then
exit(User.Get(UserProperty."User Security ID"));
end;
[NonDebuggable]
procedure UpdateUserFromAzureGraph(var User: Record User; var AzureADUserInfo: DotNet UserInfo): Boolean
var
ModifyUser: Boolean;
IsUserModified: Boolean;
TempString: Text;
begin
if IsNull(AzureADUserInfo) then
exit;
if not CheckUpdateUserRequired(User, AzureADUserInfo) then
exit;
User.LockTable();
if not User.Get(User."User Security ID") then begin
Commit();
exit;
end;
SetUserLanguage(AzureADUserInfo, User."User Security ID");
if AzureADUserInfo.AccountEnabled() and (User.State = User.State::Disabled) then begin
User.State := User.State::Enabled;
ModifyUser := true;
end;
if (not AzureADUserInfo.AccountEnabled()) and (User.State = User.State::Enabled) then begin
User.State := User.State::Disabled;
ModifyUser := true;
end;
TempString := GetFullName(AzureADUserInfo);
if LowerCase(User."Full Name") <> LowerCase(TempString) then begin
User."Full Name" := CopyStr(TempString, 1, MaxStrLen(User."Full Name"));
ModifyUser := true;
end;
if not IsNull(AzureADUserInfo.Mail()) then begin
TempString := GetContactEmail(AzureADUserInfo);
if LowerCase(User."Contact Email") <> LowerCase(TempString) then begin
User."Contact Email" := CopyStr(TempString, 1, MaxStrLen(User."Contact Email"));
ModifyUser := true;
end;
end;
TempString := GetAuthenticationEmail(AzureADUserInfo);
if LowerCase(User."Authentication Email") <> LowerCase(TempString) then begin
// Clear current authentication mail
User."Authentication Email" := '';
User.Modify();
ModifyUser := false;
IsUserModified := true;
EnsureAuthenticationEmailIsNotInUse(TempString);
UpdateAuthenticationEmail(User, AzureADUserInfo);
end;
if ModifyUser then
User.Modify();
Commit();
exit(ModifyUser or IsUserModified);
end;
[NonDebuggable]
procedure EnsureAuthenticationEmailIsNotInUse(AuthenticationEmail: Text)
var
User: Record User;
ModifiedUser: Record User;
GraphUserLocal: DotNet UserInfo;
UserSecurityId: Guid;
GraphUserExists: Boolean;
begin
// Clear all duplicate authentication email.
User.SetRange("Authentication Email", CopyStr(AuthenticationEmail, 1, MaxStrLen(User."Authentication Email")));
if not User.FindSet() then
exit;
repeat
UserSecurityId := User."User Security ID";
GraphUserExists := GetGraphUser(UserSecurityId, false, GraphUserLocal);
User."Authentication Email" := '';
User.Modify();
if GraphUserExists then begin
// Cascade changes to authentication email, terminates at the first time an authentication email is not found.
EnsureAuthenticationEmailIsNotInUse(GraphUserLocal.UserPrincipalName());
if ModifiedUser.Get(UserSecurityId) then
UpdateAuthenticationEmail(ModifiedUser, GraphUserLocal);
end;
until User.Next() = 0;
end;
[TryFunction]
[NonDebuggable]
local procedure InitializeGraphUser(UserSecurityID: Guid; ForceFetchFromGraph: Boolean)
var
UserObjectID: Text;
begin
Clear(GraphUserInfo);
if not ForceFetchFromGraph then
if UserSecurityID = UserSecurityId() then begin
AzureADGraph.GetCurrentUser(GraphUserInfo);
if not IsNull(GraphUserInfo) then
exit;
end;
UserObjectID := GetUserAuthenticationObjectId(UserSecurityID);
if UserObjectID = '' then
Error(CouldNotFindGraphUserErr, UserObjectID);
AzureADGraph.GetUserByObjectId(UserObjectID, GraphUserInfo);
if IsNull(GraphUserInfo) then
Error(CouldNotFindGraphUserErr, UserObjectID);
end;
[NonDebuggable]
local procedure CheckUpdateUserRequired(var User: Record User; var AzureADUserInfo: DotNet UserInfo): Boolean
var
TempString: Text;
begin
if not User.Get(User."User Security ID") then
exit(false);
if AzureADUserInfo.AccountEnabled() and (User.State = User.State::Disabled) then
exit(true);
if (not AzureADUserInfo.AccountEnabled()) and (User.State = User.State::Enabled) then
exit(true);
TempString := GetFullName(AzureADUserInfo);
if LowerCase(User."Full Name") <> LowerCase(TempString) then
exit(true);
if not IsNull(AzureADUserInfo.Mail()) then begin
TempString := GetContactEmail(AzureADUserInfo);
if LowerCase(User."Contact Email") <> LowerCase(TempString) then
exit(true);
end;
TempString := CopyStr(Format(AzureADUserInfo.UserPrincipalName()), 1, MaxStrLen(User."Authentication Email"));
if LowerCase(User."Authentication Email") <> LowerCase(TempString) then
exit(true);
exit(false);
end;
[NonDebuggable]
local procedure UpdateAuthenticationEmail(var User: Record User; var AzureADUserInfo: DotNet UserInfo)
var
NavUserAuthenticationHelper: DotNet NavUserAccountHelper;
begin
if IsNull(AzureADUserInfo) then
exit;
User."Authentication Email" := GetAuthenticationEmail(AzureADUserInfo);
if User.Modify() then
NavUserAuthenticationHelper.SetAuthenticationObjectId(User."User Security ID", AzureADUserInfo.ObjectId());
end;
[NonDebuggable]
procedure IsUserDelegatedAdmin(): Boolean
var
UserAccountHelper: DotNet NavUserAccountHelper;
IsUserDelegatedAdministrator, Handled : Boolean;
begin
OnIsUserDelegatedAdmin(IsUserDelegatedAdministrator, Handled); // used to mock UserAccountHelper.IsUserDelegatedAdmin()
if Handled then
exit(IsUserDelegatedAdministrator);
exit(UserAccountHelper.IsUserDelegatedAdmin());
end;
[NonDebuggable]
procedure IsUserDelegatedHelpdesk(): Boolean
var
UserAccountHelper: DotNet NavUserAccountHelper;
IsUserDelegatedHelpdeskAdmin, Handled : Boolean;
begin
OnIsUserDelegatedHelpdesk(IsUserDelegatedHelpdeskAdmin, Handled); // used to mock UserAccountHelper.IsUserDelegatedHelpdesk()
if Handled then
exit(IsUserDelegatedHelpdeskAdmin);
exit(UserAccountHelper.IsUserDelegatedHelpdesk());
end;
[NonDebuggable]
procedure IsUserDelegated(): Boolean
var
UserAccountHelper: DotNet NavUserAccountHelper;
IsDelegatedUser, Handled : Boolean;
begin
OnIsUserDelegated(IsDelegatedUser, Handled); // used to mock UserAccountHelper.IsDelegatedUser()
if Handled then
exit(IsDelegatedUser);
exit(UserAccountHelper.IsDelegatedUser());
end;
[NonDebuggable]
local procedure SetUserLanguage(GraphUserToQuery: DotNet UserInfo; UserSecID: Guid)
var
Language: Codeunit Language;
LanguageId: Integer;
begin
LanguageId := GetPreferredLanguageID(GraphUserToQuery);
Language.SetPreferredLanguageID(UserSecID, LanguageId);
end;
[TryFunction]
[NonDebuggable]
local procedure TryGetLanguageCode(CultureName: Text; var CultureCode: Code[10])
var
CultureInfo: DotNet CultureInfo;
begin
CultureInfo := CultureInfo.CultureInfo(CultureName);
CultureCode := CultureInfo.ThreeLetterWindowsLanguageName();
end;
[NonDebuggable]
procedure GetAuthenticationEmail(GraphUserToQuery: DotNet UserInfo): Text[250]
var
DummyUser: Record User;
begin
exit(CopyStr(Format(GraphUserToQuery.UserPrincipalName()), 1, MaxStrLen(DummyUser."Authentication Email")));
end;
[NonDebuggable]
procedure GetDisplayName(GraphUserToQuery: DotNet UserInfo): Text[50]
var
DummyUser: Record User;
begin
if IsNull(GraphUserToQuery.DisplayName()) then
exit('');
exit(CopyStr(Format(GraphUserToQuery.DisplayName()), 1, MaxStrLen(DummyUser."User Name")));
end;
[NonDebuggable]
procedure GetContactEmail(GraphUserToQuery: DotNet UserInfo): Text[250]
var
DummyUser: Record User;
begin
if IsNull(GraphUserToQuery.Mail()) then
exit('');
exit(CopyStr(GraphUserToQuery.Mail(), 1, MaxStrLen(DummyUser."Contact Email")));
end;
[NonDebuggable]
procedure GetFullName(GraphUserToQuery: DotNet UserInfo): Text[80]
var
DummyUser: Record User;
FullUserName: Text;
GivenName: Text;
Surname: Text;
begin
if IsNull(GraphUserToQuery.GivenName()) then
GivenName := ''
else
GivenName := Format(GraphUserToQuery.GivenName());
if IsNull(GraphUserToQuery.Surname()) then
Surname := ''
else
Surname := Format(GraphUserToQuery.Surname());
FullUserName := GivenName;
if Surname <> '' then
FullUserName := FullUserName + ' ';
FullUserName := FullUserName + Surname;
exit(CopyStr(FullUserName, 1, MaxStrLen(DummyUser."Full Name")));
end;
[NonDebuggable]
procedure GetPreferredLanguageID(GraphUserToQuery: DotNet UserInfo): Integer
var
LanguageManagement: Codeunit Language;
LanguageCode: Code[10];
LanguageId: Integer;
PreferredLanguage: Text;
begin
if IsNull(GraphUserToQuery.PreferredLanguage()) then
exit(0);
PreferredLanguage := GraphUserToQuery.PreferredLanguage();
if PreferredLanguage <> '' then
if TryGetLanguageCode(PreferredLanguage, LanguageCode) then
// If we support the language, get the language id
LanguageId := LanguageManagement.GetLanguageId(LanguageCode);
exit(LanguageId);
end;
[InternalEvent(false)]
local procedure OnIsUserDelegated(var IsDelegatedUser: Boolean; var Handled: Boolean)
begin
end;
[InternalEvent(false)]
local procedure OnIsUserDelegatedAdmin(var IsUserDelegatedAdmin: Boolean; var Handled: Boolean)
begin
end;
[InternalEvent(false)]
local procedure OnIsUserDelegatedHelpdesk(var IsUserDelegatedHelpdesk: Boolean; var Handled: Boolean)
begin
end;
}