在前篇文章[Azure] 同步本地端AD與Azure AD的帳號與群組資訊中,說明了如何透過Azure AD Connect將本地端的AD資料與Azure AD進行同步
本篇文章則會將同步至Azure AD上的使用者帳號資訊,透過程式的方式呼叫微軟提供的Graph API,取得使用者的帳號基本資訊、工作資訊,以及上層主管
在使用Graph API上,一共會使用到三個Graph API,分別是Get users、Get a user以及Get a user's manager
首先,先打開傳統的Azure管理介面,並進入Azure AD的內容,建立一個新的應用程式,應用程式類型選擇"WEB 應用程式和/或 WEB API"
接著下一步,給予登入URL與應用程式識別碼URI,這些值可以先隨意設定,稍後也可以進行修改
完成設定後,點選進入該應用程式的設定畫面,並將"用戶端識別碼"記起來,然後建立一組金鑰,當然金鑰值一樣先將它記下來
接著,確認一下本地端的AD是否有將資訊同步至Azure AD上,在這個範例裡,我建立了一個使用者帳號john,其主管為Bill,公司名稱與部門職稱等等的,都有設定進去
Azure AD上也已經同步完成,並保有本地端的工作資訊設定了
接著,打開Visual Studio,並建立一個Windows Form的專案,加入一個AzureADUtility.cs的類別庫,並將下面程式碼加入至該類別庫中
public class AzureADUtility
{
public string Tenant { get; set; }
public string ClientId { get; set; }
public string Secret { get; set; }
HttpStatusCode code;
public AzureADUtility(string strTenant, string strClientId, string strSecret)
{
this.Tenant = strTenant;
this.ClientId = strClientId;
this.Secret = strSecret;
}
public Models.User.Result GetUsers()
{
string strUrl = "https://graph.windows.net/" + this.Tenant + "/users?api-version=1.6";
Models.User.Result objResults = JsonConvert.DeserializeObject<Models.User.Result>(this.CallGraphAPI(strUrl, "GET", "", out code));
return objResults;
}
public Models.User.Manager GetManager(string strObjectId)
{
string strUrl = $"https://graph.windows.net/{this.Tenant}/users/{strObjectId}/$links/manager?api-version=1.6";
string strContent = this.CallGraphAPI(strUrl, "GET", "", out code);
Models.User.Manager objMng = null;
if (code == HttpStatusCode.OK)
objMng = JsonConvert.DeserializeObject<Models.User.Manager>(strContent);
return objMng;
}
protected string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.microsoftonline.com/" + this.Tenant);
var thread = new Thread(() =>
{
result = context.AcquireToken("https://graph.windows.net", new ClientCredential(this.ClientId, this.Secret));
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
return result.AccessToken;
}
protected string CallGraphAPI(string strUrl, string strHttpMethod, string strPostContent, out HttpStatusCode code)
{
string token = GetAuthorizationHeader();
HttpWebRequest request = HttpWebRequest.Create(strUrl) as HttpWebRequest;
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
request.Method = strHttpMethod;
code = HttpStatusCode.OK;
if (strPostContent != "" && strPostContent != string.Empty)
{
request.KeepAlive = true;
request.ContentType = "application/json";
byte[] bs = Encoding.ASCII.GetBytes(strPostContent);
Stream reqStream = request.GetRequestStream();
reqStream.Write(bs, 0, bs.Length);
}
string strReturn = "";
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
var respStream = response.GetResponseStream();
strReturn = new StreamReader(respStream).ReadToEnd();
}
catch (Exception e)
{
strReturn = e.Message;
code = HttpStatusCode.NotFound;
}
return strReturn;
}
}
這個類別庫的程式碼,主要是用來取得使用者帳號資料,以及上層主管資料用的程式碼,當然還包含了呼叫Graph API的部份
接著建立第二個類別庫Models\User.cs,並將下面的程式碼加入至該類別庫中
public class User
{
public class Value
{
public string odatatype { get; set; }
public string objectType { get; set; }
public string objectId { get; set; }
public object deletionTimestamp { get; set; }
public bool accountEnabled { get; set; }
public object[] signInNames { get; set; }
public Assignedlicens[] assignedLicenses { get; set; }
public Assignedplan[] assignedPlans { get; set; }
public object city { get; set; }
public string companyName { get; set; }
public string country { get; set; }
public object creationType { get; set; }
public string department { get; set; }
public bool? dirSyncEnabled { get; set; }
public string displayName { get; set; }
public object facsimileTelephoneNumber { get; set; }
public string givenName { get; set; }
public string immutableId { get; set; }
public object isCompromised { get; set; }
public string jobTitle { get; set; }
public DateTime? lastDirSyncTime { get; set; }
public string mail { get; set; }
public string mailNickname { get; set; }
public string mobile { get; set; }
public string onPremisesSecurityIdentifier { get; set; }
public string[] otherMails { get; set; }
public string passwordPolicies { get; set; }
public Passwordprofile passwordProfile { get; set; }
public object physicalDeliveryOfficeName { get; set; }
public object postalCode { get; set; }
public string preferredLanguage { get; set; }
public object[] provisionedPlans { get; set; }
public object[] provisioningErrors { get; set; }
public string[] proxyAddresses { get; set; }
public DateTime refreshTokensValidFromDateTime { get; set; }
public string sipProxyAddress { get; set; }
public object state { get; set; }
public object streetAddress { get; set; }
public string surname { get; set; }
public object telephoneNumber { get; set; }
public string thumbnailPhotoodatamediaContentType { get; set; }
public string usageLocation { get; set; }
public string userPrincipalName { get; set; }
public string userType { get; set; }
public string ManagerUri { get; set; }
public string ManagerObjectId { get; set; }
public string Manager { get; set; }
}
public class Passwordprofile
{
public object password { get; set; }
public bool forceChangePasswordNextLogin { get; set; }
public bool enforceChangePasswordPolicy { get; set; }
}
public class Assignedlicens
{
public object[] disabledPlans { get; set; }
public string skuId { get; set; }
}
public class Assignedplan
{
public DateTime assignedTimestamp { get; set; }
public string capabilityStatus { get; set; }
public string service { get; set; }
public string servicePlanId { get; set; }
}
public class Result
{
public string odatametadata { get; set; }
public Value[] value { get; set; }
}
public class Manager
{
public string odatametadata { get; set; }
public string url { get; set; }
}
}
這個類別庫主要目的是為了定義出從Graph API上取得資料的JSON資料格式,方便轉為物件讓我們使用
在拉置畫面的動作中,我們在Windows Form上放一個DataGrid以及三個文字欄位,分別是設定網域的Tenant、剛剛在Azure上記錄的"用戶端識別碼"與"金鑰",並加入兩個Button,分別是取得AD上的使用者,以及其主管的動作
接下來在Get AAD Users的動作中,呼叫AzureADUtility.cs裡的GetUsers()這個程式
User.Result objResult;
AzureADUtility objAAD;
public frmMain()
{
InitializeComponent();
objAAD = new AzureADUtility(txtTenant.Text, txtClientId.Text, txtSecret.Text);
}
private void btnGetAADUsers_Click(object sender, EventArgs e)
{
objResult = objAAD.GetUsers();
gvUsers.DataSource = objResult.value;
}
這段主要的目的在透過呼叫Graph API後,將取得的所有使用者帳號資料列在DataGrid上,從下圖就可以確認到,在這個網域中的帳號資料都已經被取出,並列在DataGrid之中,當然也包含了工作資訊,如行動電話等等的內容
接著,我們在Get Users Manager的按鈕動作上,加入下面的程式碼
private void btnGetManager_Click(object sender, EventArgs e)
{
// 取出該帳號的ObjectId, 並透過GraphAPI取得主管的資訊
for (int i=0; i<objResult.value.Length; i++)
{
string strObjectId = objResult.value[i].objectId.ToString();
// 呼叫GraphAPI,取得主管的資訊
User.Manager objManager = objAAD.GetManager(strObjectId);
if (objManager != null)
{
// 取代回傳的Url,並取出主管的ObjectId
string strManagerObjectId = objManager.url.Replace("https://graph.windows.net/" + txtTenant.Text + "/directoryObjects/", "");
strManagerObjectId = strManagerObjectId.Replace("/Microsoft.DirectoryServices.User", "");
// 找出主管的資料
User.Value objUser = objResult.value.FirstOrDefault(x => x.objectId == strManagerObjectId);
// 重新放入欄位
objResult.value[i].ManagerUri = objManager.url;
objResult.value[i].ManagerObjectId = strManagerObjectId;
objResult.value[i].Manager = objUser.displayName;
}
}
gvUsers.DataSource = objResult.value;
}
由於透過Graph API取得主管資訊的動作,並不是直接回傳該主管的資訊,而是回傳主管資訊的網頁連結,就如同Graph API的頁面上所提供的資訊一樣,所以必須在程式中將取得的url字串置換掉,保留該主管的Guid即可,因為有了Guid,就可以再透過Graph API找出該物件的基本資訊
最後可以看到結果,連主管的資訊都可以取得了
本地端的AD同步至Azure AD並進行單一登入的作法有越來越多公司開始採用這樣的運作模式,當然也會有越來越多的工作流程與組織內容會依賴現有的AD資訊,並同步至Azure上作更多的利用
透過Graph API的操作,可以更有效的利用Azure AD上取得資訊的介面,達到更大的運用彈性
範例程式已放上Github:https://github.com/madukapai/maduka-Azure-AD