2018-05-02

From Identity Management to Identity Governance

有關認證與授權,各種名詞跟負責的功能常常處於混亂的狀況;比較清楚的多半是偏技術面的通訊協定,其他諸如產品名稱與宣稱功能往往令人迷惑,常見的有:
  • Identity Management (IdM) [1]
  • Identity and Access Management (IAM)
  • Access Management (AM)
  • Identity Governance and Administration (IGA)

首先我們想釐清的重點有兩個:
  • Directory service 與上述各系統之間的關係;
  • 各系統的差異為何?

一般來說,組織或企業在踏入這塊領域之前,通常會預先建置 directory service,就通訊協定標準來說,LDAP 是主流;但由於微軟 Active Directory (AD) 的超高市佔率,AD 儼然成為另一種行業 (de facto) 標準。但即使建置了 directory service,對於企業 IT 而言仍有許多不足的地方:
  • 首先從認證與授權的角度來看,directory service 僅能處理認證;雖然 directory service 除了記錄使用者的帳號 & 密碼外,也提供許多 attributes,甚至是群組關係,但是否讀取這些屬性作為授權依據,傳統上仍由應用系統控制,亦即應用系統可自行決定是否讀取 directory service 上的資訊,並決定授予哪些權限。
  • 其次就帳號是否生效來說,企業內部往往涉及更多人事作業的需求,例如:預約到職或離職、留職停薪、休假等,無法單純地對應成在 directory service 上新增或刪除一個使用者;這裡頭往往需要一些前置作業,例如:資料匯入、流程簽核、發送通知等,才能順利完成。

於是 Identity Management (IdM) 因應而生。IdM 不僅滿足於 directory service 的圖形化操作介面,更包括前面提的資料匯入、流程簽核、發送通知等功能,方便人事在單一系統操作,完成帳號開通或停用,但仍未處理授權議題。

為了解決授權問題,部分廠商提出了 IAM;但受限於應用系統中授權模組設計,IAM 多半只能利用使用者的 attributes,呼叫各應用系統的 API 產生對應的角色,最後由授權模組在實際操作時給予權限。

因此 IAM 的重點在於針對各個應用系統實作相應的 connectors,例如 OneLogin 便具備與 AWS, Atlassian, G Suite, Office 365, Salesforce, Trello 等應用系統串接能力 [2]。這裡頭有關讀取使用者 attributes 到建立應用系統角色的過程,稱為 entitlement provisioning [3];IAM 亦提供圖形介面方便設定規則讓 connectors 定期更新應用系統角色。

前兩者大概處理完認證與授權的問題,最後兩個分別可以視為 IdM 與 IAM 的再進化。

傳統 IdM 因為未擴充通訊協定,只提供 LDAP 認證逐漸無法滿足用戶登入時的便利與安全性需求;在現代的資訊系統中,常見的認證需求主要有:single sign-on (SSO) 與 muti-factor authentication (MFA),甚至是 one-time password (OTP);以 okta 為例,在 SSO 方面就支援了 SAML 2.0 與 OpenID,同時也支援簡訊、Email、手機推播、U2F token、指紋等多種多重認證行為 [4]

如果說 AM 側重於用戶端登入行為,IGA 則側重使用者資訊導入應用系統時的管理。

前面提到 IAM 的重點在於 entitlement provisioning,然而 provisioning 的行為類似 IdM 啟用或停用帳號,也需要資料匯入、流程簽核、發送通知等;亦即並非每一位在 LDAP service 建立帳號的使用者,都具備登入所有系統的權限,各應用系統本身對於部分角色授予,可能也需要相應的簽核流程。

也就是說,IGA 針對各應用系統的 provisioning 提供了差異化管理,同一個使用者可能在某些系統可以直接登入獲得角色,部分系統需要簽核後才有角色,還有一些系統無法登入。

see also:
[1] Wikipedia: Identity Management
[2] OneLogin: App Integration
[3] OneLogin: Provisioning Entitlements
[4] Okta: Adaptive Multi-Factor Authentication

2018-04-10

Salesforce: Record Type and View Model

先前 [1] 用物件導向的觀念分析了一下 Salesforce 中 object, record type, and master-detail object relationships 的特性;就 Salesforce 系統設計而言,是否能表達物件導向並不重要,讓使用者易於設定、儘快進入使用,才是這類 domain-specific service 的重點。

如果用三層式架構中 domain model, data model, and view model 的角度來看,Salesforce 在 data model 原則上採用類似 active record pattern 的作法,設定畫面中的 object 直接對應到 Apex programming 中的 object type 與 SOQL 中的 table,以 1-to-1 的方式呈現。

這時候 record type 很明顯就是為了 view model 而存在,只是多數 Web application development 中,有很多好用的 model mapping libraries 可供使用,view model 與 domain model 之間的關係可以很彈性地為 many-to-many。回到 Saleforce,如果要讓使用者在不需任何程式碼,也能保有 view model 的便利性:
同一個 domain model 可搭配不同的畫面,顯示不同的屬性組合。
這時候就可以透過 record type 的設定,讓 view model 與 domain model 之間達到 many-to-one 之間的關係。

see also:
[1] Salesforce: Objects Generalization and Specialization

2018-02-11

LDAP Basics

LDAP [1] 在現在 IT 系統應用中,幾乎快變成「計概」等級的基礎服務,但是聽過的人多,知道用途的人可能也不少,但是能明確知道應用場域跟侷限的人就不多了;甚至很多人只是看著操作說明,一步步設定起來,對於基礎名詞處於一知半解。

LDAP 顧名思義,它是脫胎自 Directory Access Protocol (DAP;定義於 X.500 [2],a.k.a ISO/IEC 9594-1),最早可追溯到 1990 的版本 [3],幾乎所有基本名詞都在 X.500 就定義好了,這邊整理我認為最重要的三個基本名詞。

ObjectClass
在 LDAP 中,每一筆看到的資料 (entry),舉凡人員或組織,都是一堆屬性 (attribute) 的組合,而個別 entry 必須定義那些屬性?可以擁有那些屬性?則由該 entry 所擁有的 objectclass 決定。(objectclass 本身也是 entry 的屬性之一,特別的是這個屬性是預設必要的,用這個設定值來控制是否帶入其他屬性。)

如同 object-oriented programming 一樣,這邊的 class 裡頭會定義 instance (也就是前述的 entry) 擁有的 fields (前述的 attribute);classes 之間也有繼承關係,可以讓 sub-classes 直接繼承 base class 的所有 attributes 而不用重複宣告。相對於 OOP 中的 extends,這邊繼承時使用的關鍵字是 SUP,如果單純作為 LDAP 使用者不看 LDIF 對 objectclass 定義的話,可能不會接觸這個詞。

不一樣的是:ObjectClass 中並不定義行為 (methods),也沒有 constructor;因此當定義 entry 時,採直接描述 entry 的各個屬性,包括 objectclass 與 attribute。底下是一個 displayname 為 /QNAP 的 organization entry:
dn: O=QNAP
objectclass: organization
objectclass: top
displayname: /QNAP
其中 objectclass 欄位即這個 entry 擁有的「型別」,一個 entry 可以同時擁有多個 objectclass 屬性;但 ObjectClass 底下細分成 ABSTRACT, AUXILIARYSTRUCTURAL 三種,一個 entry 同時間只能有一個 objectclass 屬性為 STRUCTURAL,類似 OOP 中做為 initiate 的 concrete class。

Inheritance of ObjectClass

至於 AUXILIARY 則跟 OOP 中的 interface 類似,用於讓 entry 帶上需要的屬性,而 ABSTRACT 則類似 root interface 或是 Java 中 Object 這個 class。ABSTRACT ObjectClass 一般不會自行定義,僅沿用系統中預帶的 top ABSTRACT ObjectClass。

AttributeType
相對於 ObjectClass 定義 compound type 的結構,AttributeType 可視為用於定義 scalar type。注意:LDAP 的所有 ObjectClasses 都只能是單層結構,不允許擁有型別為其他 compound type 的 fields。

此外,AttributeType 的定義中會指明套用的 equality rule,類似於 OOP class 中常實作的 equals() method。如同 ObjectClass,AttributeType 也可以繼承其他的 AttributeType,用於直接延用 base attribute 的 equality rule。

MatchingRule
即前述 equality rule 的實作,常見的有:
  • caseExactIA5Match
  • caseIgnoreMatch, caseIgnoreIA5Match, caseIgnoreListMatch
  • distinguishedNameMatch
  • facsimileTelephoneNumber, integerMatch, telephoneNumberMatch 等。

了解這三個幾本名詞後,下一步就能對 LDAP 中如何依賴 distinguish name (DN) 定位一個 entry,以及 entries 如何形成 directory information tree (DIT) 樹狀結構進行了解了。

see also:
[1] Wikipedia: LDAP
[2] Wikipedia: X.500
[3] ISO/IEC 9594-1:1990
[4] Understanding the LDAP Protocol, Data Hierarchy, and Entry Components
[5] LdapWiki.com

2018-02-07

C# URL Encoding (Con't)

前一篇 [1] 提到當使用 URL 傳參數遇到一些特殊字元時,.NET framework 提供了幾種編碼方式都不能完整處理,需要另行寫一段轉換程式,將參數另行處理,甚至需要將 IIS 的 allowDoubleEscaping 打開方能順利執行。

為了避免掙扎於 IIS 安全機制與參數編碼難以兩全的問題,同事幫忙找到在編寫轉換程式時可以使用 HttpServerUtility.UrlTokenEncode() 處理參數;雖然還是得自己寫一段程式,但可以不用更改 IIS 設定。本質上這個編碼接近 Base64 [2],但是因為原始 Base64 仍會生成某些特殊字元 (+, /, =),因此這裡等於是 Base64 的變種。

關於這三個 class 與 .NET framework 版本的關係,稍微整理了一下:
Class Name.NET Framework.NET Core
System.Web.HttpServerUtility1.1N/A
System.Web.HttpUtility1.12.0
System.Uri1.11.0

附帶一提:微軟推出新版的 API Browser,將原先在 MSDN 上的 API 文件改放到 docs.microsoft.com;新版的站台提供較多有關 .NET Core 或 PCL 版本的資訊,但也捨棄了 .NET framework 4.5 以前的資訊,因此查不到各別 class 起源於哪個版本的 .NET framework。

see also:
[1] C# URL Encoding
[2] Wikipedia: Base64
[3] HttpServerUtility: MSDN, docs.microsoft.com
[4] HttpUtility: MSDN, docs.microsoft.com
[5] Uri: MSDN, docs.microsoft.com

2018-01-16

C# URL Encoding

被這東西咬第二次了,想想還是釐清楚記下來。

這個議題事實上已經被講很多了,寫得最好得應該是 [1];候選的函式有:HttpUtility.UrlEncode()、Uri.EscapeUriString()、Uri.EscapeDataString(),主要差異在於:
  1. 適用的輸入是 URL 整體,還是個別參數? 上面三個只有 Uri.EscapeUriString() 不會對 ':' 跟 '/' 字元編碼,可以用在 URL 整體,另外兩者都不行。
  2. 會處理的字元跟未編碼字元各有哪些?HttpUtility.UrlEncode() 跟 Uri.EscapeDataString() 除了會對 ':', '/' 編碼外,還會處理 ' ' (space), '?', '+', ' (single quote),對於 '.' (dot) 都不處理。
Uri.EscapeUriString() 雖然可以直接丟整個 URL 進去,但主要問題在於:太多特殊字元都不處理,包括 ' (single quote), '+', '?' 等。至於在 HttpUtility.UrlEncode() 跟 Uri.EscapeDataString() 間選擇時,差異不大,只有大小寫跟空白字元處理方式,兩者都是合法的,可以自由挑選;但也都因為不能處理完整的 URL,所以需要自己另寫一段程式 [1]
  • 分離 query parameter 與原本的 URL;
  • 將 query parameter 的 key 與 value 都使用 Uri.EscapeDataString() 編碼。
注意:範例中的程式碼不處理 path parameter,如需套用至 path parameter 應該也是可行,但當使用 encoded string 發出 request 時,IIS 預設的 request filtering 會擋掉該 request 並拋出 HTTP substatus 404.11 (URL Double Escaped),需要將 allowDoubleEscaping 設定為 true 才能通過。

Encoding vs Double Escaping?

這邊需要釐清兩件事:
  • 何謂 double escaping?
  • 將 URL 中的 query parameters 編碼是否屬於 double escaping?
由於在 RFC 2396 [2] 中,將特殊字元轉換為 '%' 加上 16 進位數字的動作稱為 escaped encoding,因此 HttpUtility 使用了 encode 這個動詞,而 Uri 與 IIS request filtering 使用 escape,實質上是可以互換通用的。

其次,一般只呼叫一次 Uri.EscapeDataString() 的結果字串屬於 single escaping,並不符合 double escaping 的條件,但由於 ' ' (space) → '+' (plus sign) → %25 這個轉換路徑,IIS request filter 誤以為 %25 是源自 ' ' (space),歷經 double escaping 得到,因此拋出 404.11 錯誤。

這邊有一篇文章 [3] 寫得非常仔細,將 request URL 經 Windows Http.sys、IIS Request Filtering、IIS Core、Handlers 等四層處理過程都詳細解說,值得一讀。

see also:
[1] Huan-Lin 學習筆記: URL 編解碼問題
[2] RFC 2396
[3] Use of special characters like '%' ‘.’ and ‘:’ in an IIS URL

2018-01-04

Kayako Cron Tasks Management

傳統 web application 實作 cron jobs 後,驅動源頭大多來自 web container 或作業系統 crontab 設定,由 application 外部負責定期啟動內部的 cron task manager,看看是否有哪個 cron task 該被執行。

Kayako 在 cron task manager 以下的實作跟一般作法沒有什麼不同,比較特別的是:它把 cron task manager 暴露成一個 Web API (https://site/cron/index.php?/Base/CronManager/Execute) 讓瀏覽器直接執行,而不仰賴 web container 或作業系統設定。

至於執行的時機與頻率,Kayako 把這段邏輯包在 https://site/Core/Default/Compressor/js 中,讓每個頁面都 include 這段 JavaScript,也就是當系統任何一個畫面被瀏覽器訪問時,都會同時執行一次內部的 cron task manager。

Cron task manager 的 Web API 先由 Controller_CronManager 處裡,接著進入真正的 SWIFT_CronManager::RunPendingTasks()。