2021-06-09

ffmpeg Options

 筆記一下 ffmpeg 時常用參數:

  • -i url: Set the file URL of input. When capture video from X11, the url is comprised of [hostname]:display_number.screen_number. The hostname is localhost by default, and the display_number.screen_number is as known as display_name and could be found by the command xdpyinfo.
  • -f format: Force input or output file format. The format could be x11grab to grab video from the X11 display.
  • -s size: Set the frame size.
  • -r fps: Set the frame-per-second.
  • -filter[:stream_specifier] filtergraph: Define a filtergraph for the stream.
  • -crf value: Set the constant-rate-factor quantizer scale.
  • -preset name: Set the collection name of compression ratio options.
  • -vcodec codec: Set the codec for output video.
  • -g size: Set the size of group-of-pictures.
  • -y: Overwrite output files without asking.

References:

[1] https://ffmpeg.org/ffmpeg.html
[2] https://trac.ffmpeg.org/wiki/Encode/H.264

2021-02-03

SSH Tunnel

當 service provider 與 service consumer 之間被防火牆阻隔,導致無法直接連線時,SSH tunnel 是個常見的處理方式,其中又分為 local forwarding 與 remote forwarding。無論是哪一種 forwarding 模式,都需要有一台同時可與 provider 與 consumer 直接連線的 SSH server 作為中繼,差別在於由哪一方負責執行 SSH client 建立 tunnel;而建立 tunnel 時,需指定一個本機的 port number,與遠端 hostname 與 port number。

Local Forwarding

由 service consumer 執行 SSH client 建立 tunnel。

可能使用情境為:應用系統的資料庫 (service provider) 僅允許應用系統連線存取,且需從自己的電腦 (service consumer) 連線查詢資料,此時本機執行 SSH client 與 database client,分別於本機新增一 listening port 將封包轉送至資料庫,並將 database client 連線指向本機的 listening port。

ssh -L localhost:9001:service.provider:3306 ssh.server

由於 service porvider 的 port number 為 consumer 端建立 tunnel 時輸入,因此 consumer 端可透過輸入其它 port number 甚至是 hostname 達到連線至其它系統的目的。

Remote Forwarding

由 service provider 執行 SSH client 建立 tunnel。

可能使用情境為:內網資訊系統需要請外部廠商連線維護時,希望嚴格限制 consumer 可連線的 hostname 與 port number;此時可於內網執行 SSH client,指定 SSH server 上需新增的 listening port 與內網 service provider 的 hostname 與 port number,service consumer 連線至 SSH server 上的 listening port 時,最終只能被轉傳至 service provider。

ssh -R 9001:service.provider:443 ssh.server

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, AUXILIARY 跟 STRUCTURAL 三種,一個 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()。

2017-12-28

Kayako Language Engine and Caching System

先前處理 Kayako email 發送的多國語系問題時,採用新建 LanguageEngine instance,透過 LanguageEngine instance 抓取所需詞彙的方式完成。後來發現,這樣的解法會影響其他信件的發送,例如:ticket auto-close 時的通知信件,會因無法抓到所需詞彙而失敗,追根究柢後發現:無論產生多少新的 LanguageEngine instances,底層實際載入與儲存詞彙都使用相同一塊空間,無法避免動態切換語系時交互影響。

上圖是 LanguageEngine 與 SWIFT caching system 之間的關係。

  1. 首先 LanguageEngine 在載入詞彙時會提供 Laod() 或 LoadQueue() 供外部呼叫,最終都落到 SWIFT 的 caching system,使用 SWIFT_CacheStore->LoadQueue()。
  2. SWIFT_CacheStore 裡頭涉及 2 內部變數:CacheMemory (SWIFT_CacheMemcache) 與 Registry (SWIFT_Registry);因為目前系統並未設定 MEMCACHE_HOST,因此這邊只會使用 Registry 作為實際儲存位置。
  3. SWIFT_Registry 內部的 storage 分為兩層,level 1 的是 in-memory 的 _registryCache,level 2 是 database 的 swregistry table。
這邊要注意:SWIFT_Registry instance 是在 SWIFT 中生成,因此無論每次執行無論處理過多少張 tickets 都會使用相同的 SWIFT_Registry instance,且單一 SWIFT_Registry instance 中,每一個 key 值只會對應到一個 value。

為了避免語系切換時,汙染 SWIFT_Registry instance 中的語言詞彙,因此決定改採直接 SQL query 的方式載入所需語言。

ps. 這邊的 caching system 指的是 data cache,Kayako 在 4.0 剛推出時並沒有針對 Memcached 整合的功能;倒是官方推薦使用針對 code cache 的 XCache [1]。相較於新版 PHP (>5.5) 更被推崇的 code caching system 是 opCache [2]。

see also:
[1] Kayako Forum: V4 Very Slow!
[2] StackExchange: PHP 7 opCache v PHP 5.6 XCache

2017-12-10

.NET Standard 2.0

先前 [1] 分別從 framework 與 virtual machine 兩個面向聊過 .NET framework 的角色,其中提及 Common Language Infrastructure (CLI) 的三大組成:Common Intermediate Language (CIL), standard libraries (BCL and FCL), 與 Common Language Runtime (CLR)。

最近因為 .NET Core 2.0 的正式發佈,越來越多人開始回頭看原本 .NET framework 的組成,以及各種實作與標準之間的關係;這兩天剛好看了 InfoQ 上的一篇文章 [2];再繼續之前,首先要抓準一個概念:現行的 .NET 平台實作不只 .NET framework 一家,還有 .NET Core, Mono, Xamarin 等等,而各家實作在跨平台的特性與實現方法上也不盡相同,根本原因在於一開始的 CLI standard library 以大鍋菜的方式設計,雖然有 BCL 與 FCL 之分,但 BCL 並未很好地把 Windows dependent 的功能剝離出去,導致後續跨平台 runtime 實作困難。

以下是順手整理的心得:
  • .NET Standard 重新定義了 BCL 的標準,讓各家 (.NET Framework, Xamarin, and .NET Core) 實作的 BCL 趨於一致。
  • 針對 BCL 之上的 application models,.NET Core 捨棄 Windows Forms 與 Windows Presentation Foundation (WPF),僅保留 Console,並實作 APS.NET Core [3]。
簡單來說,.NET Standard 是未來跨平台元件或應用程式開發時的必要 (但非充要) 條件,畢竟一個可以正常運作於 .NET Standard 實作上的元件,仍可能依賴該實作的特定函式庫。

至於 Portable Class Libraries (PCL) 則是針對既有 .NET 平台實作所提的暫時方案;透過 Visual Studio 與 PCL 的幫助,Visual Studio 可自動將程式中 dependency 對應到各家實作的函式,免去開發人員額外偵測與判斷的痛苦。有關 PCL,個人認為 InfoQ 文章中參照的 [4] 寫得比較完整。

附帶一提:當微軟在 2017/8/13 發佈 .NET Standard 文件 [5] 時,裡頭的對應表可能讓人誤以為 .NET framework 4.6.1 就已經實作 .NET Standard 2.0;實際上,在 .NET framework 4.6.1 尚須加裝 .NET Core 2.0 SDK 才能支援,否則會缺少 200 多個 API 實作。因此真正的完整支援要到 .NET framework 4.7.1 才算完成 [7]。

see also:
[1] .NET: Framework or Virtual Machine
[2] InfoQ: .NET Core and .NET Standard: What Is the Difference?
[3] .NET Standard - Demystifying .NET Core and .NET Standard
[4] Understanding .NET Core, .NET Standard, .NET Core applications and ASP.NET Core
[5] Microsoft Docs: .NET Standard
[6] Microsoft .NET Blog: Announcing the .NET Framework 4.7.1

Salesforce SOSL, SOQL, and Tokenization

Salesforce 在登入後的畫面頂端提供了一個搜尋框,提供使用者搜尋各式資訊,例如:Account, Contact, Opportunity, Contract 等。

但由於預設的搜尋行為使用其內部的斷詞系統,因此針對一些專有名詞搜尋時,可能就不那麼盡如人意;例如:當資料為東京都時,可以用東京或完整的東京都搜尋到,但是無法用京都搜尋到該筆資料,或許這在很多時候是好用的,尤其當大家公認東京都跟京都是完全不同的 entity,但有時候當使用者只知道公司部分名稱時,就容易造成困擾。以下是 Salesforce 搜尋框的兩個主要限制:
  • 預設行為為 start with:一如前例,系統搜尋行為會找出以京都為起始字串的資料,如果想找到東京都,搜尋時必須輸入「*京都」。
  • 底層以 morphological tokenization 為基礎的索引:換言之,當專有名詞不常見時,可能因斷詞系統無法正確辨識而找不到。

上述兩個問題在斷詞系統剛從 bigram 轉換為 morphological [1] 時備受批評,但在 2017 年 4 月 [2] 跟 6 月 [3] 有修正得更精細些,例如:「横浜市中区」已經可以使用「中区」找到,關鍵不只是 start-with 的問題,更在於斷詞系統能識別「中区」一詞;然而對於廣大可能的中文專由名詞來說,這樣的解法仍然不夠。

其實英文也不見得就完全沒事,因為有時候在專有名詞欄位,使用者可能只輸入縮寫,甚至是自創的縮寫,這時候 morphological 一樣無法正確辨識並提供搜尋時所需的索引。

想要徹底解決,自己動手寫點程式可能免不了,就 Salesforce 提供的 SOSL 與 SOQL 而言,前者仍然以斷詞系統為基礎 [4],因此只能選用 SOQL 的 like-anywhere 完成,但相對效能上難免吃重些。

see also:
[1] Salesforce Release Notes (Winter'15): Users Searching in Chinese, Japanese, Korean, and Thai Find Optimized Results
[2] Salesforce Community Known Issues: New morphology tokenization for Japanese characters after new search framework can cause unexpected morphological tokenization result
[3] Salesforce Community Known Issues: Chinese - Morphology tokenization improvement for mainly proper name noun
[4] Force.com SOQL and SOSL Reference: Introduction to SOQL and SOSL
[5] Salesforce Community Known Issue: Wrong results for Japanese OR search for file content
[6] ROSETTE.com: Why Tokenization Matters

2017-12-08

Unit Test for Kayako

接手 Kayako Fusion 這套系統超過一年了,一直沒有作單元測試,主要原因有以下兩個:
  1. License 驗證:幾乎所有測試需求都跟底層的 SWIFT framework 有關,而要載入這個 framework instance 的前提是 license 被驗證為有效。
  2. 缺乏文件:雖然它們家的 SWIFT_TestCase 也是繼承自 PHPUnit_Framework_TestCase,但不知如何執行。
然而直接在 UI 端作 acceptance testing 就不用煩惱這個問題,只要修改 client 端的 host 設定,將 license 中的 domain name 綁定測試機 IP。這時候即使測試機上設定其它 hostname,只要 client 端瀏覽器使用 license 的 domain name 連道測試機,就可以正常執行。

最近因為修改的東西愈趨複雜,很多問題無法單純在測試機上透過 acceptance testing 讓問題浮現,往往都是 patch 上正式機才爆出,因此需要回頭徹底解決,步驟上大致如下:
  1. 安裝 PHPUnit;因為 PHP 版本還在 5.4 系列;只能安裝 PHPUnit 4.8.36 [1] [2] [3]。
  2. 修改 $KAYAKO_HOME/tests/index.php;因為 license 檢驗,會比對 license 中的 domains 與 $_SERVER['HTTP_HOST'],而在 UI 端作 acceptance testing 時,$_SERVER['HTTP_HOST'] 來自 HTTP request header 中的設定 [4] [5],改執行 unit testing 時缺少真正的 request header,因此要在 index.php 中自行設定,並作為執行時的 bootstrap script。
  3. 執行範例如下:/usr/local/bin/phpunit.phar --bootstrap ./index.php ./__swift/library/Date/class.SWIFT_DateTest.php
官方預帶了一些 test cases,分別針對 KQL, KQL2, Cron, Date,可以針對需求仿造。

後續可能要再看一下 mock object 與相關 inject testing database 的方法,這樣就可以把單元測試這塊補上。

see also:
[1] PHPUnit
[2] PHPUnit Download
[3] PHPUnit 4.8 Manual (PDF)
[4] PHP.NET: $_SERVER
[5] StackOverflow: $_SERVER['HTTP_HOST'] not set
[6] Kayako Classic User Guide
[7] Kayako Developer Resources

2017-11-05

Salesforce Secured Callout

在應用系統介接時,使用 Web API 是目前常見的手段;Salesforce 也提供這樣的模式,無論是扮演 service provider (使用內建的 SOAP Web Services,或是自訂 RESTful API),或是 service consumer,都有對應的支援;其中當 Salesforce 扮演 service consumer 時,最常見的是使用 Apex 自行呼叫外部 Web API,這種呼叫模式在 Saleforce 中稱為 callout。

在安全性方面,系統介接時一般會優先討論連線方面的安全性,更嚴謹的才會觸及資料面的安全性;這邊我們只討論前者,也就是透過 TLS 對 HTTP 加密,這樣的外部呼叫稱為 secured callout。

值得注意的是:Salesforce 在實作 secured callout 時,對簽發 target service 憑證的 CA 只認可正向表列中 root CA 簽發的憑證 [1] [2],然而這份列表有些陳舊 [3],許多常見的 root CA 都不在列表中,例如 TWCA Global Root CA,若 target service 不是自有的服務,無法變更憑證時,可能會被迫使用未加密的 HTTP。

另外在 protocol 版本上,Salesforce 支援 TLS 1.1 與 1.2 [4],TLS 1.0 由於可降階至 SSL 3.0 且 SSL 3.0 設計上有安全性缺陷,因此目前已停用 TLS 1.0 [5]。

除了 secured callout 外,另一個會使用到憑證的場合是:使用 Salesforce 實作自訂的網站,並掛上自家的網域名稱;這時候對於憑證的限制比前者稍微放寬,允許不是由 root CA 直接簽發的憑證。管理者可以新增所需的 intermediate CA 到 Salesforce [6],但根源的 root CA 仍必須是列表中認可的。

至於新增 custom site 憑證到 Salesforce 時,由於這邊扮演的是 service provider,因此上傳的資訊中必須包括 private key;使用的檔案格式是 Java KeyStore,這方面的基礎知識可以參考 [7]。

see also:
[1] Outbound Messaging SSL CA Certificates
[2] Know More about All the SSL Certificates That Are Supported by Salesforce
[2] Salesforce Community Idea: Manage List of Trusted Certificate Authorities 
[3] Summer '15 Release Notes: Test and Use Advanced Networking Protocols
[4] Salesforce Disabling TLS 1.0
[6] SSL Certificate Standards
[8] Making Authenticated Web Service Callouts Using Two-Way SSL

Salesforce Release Cycle and Certification

Salesforce 維持每年三次的更新節奏 [1],分別為:
  • Spring Release: February
  • Summer Release: June
  • Winter Release: October
其中 Winter release 會標示來年的年份,例如 2017/10/16 發表的 Winter release 會標示成 Winter'18。

此外,每次新版發表後,官方會給予所有證照持有人約 8 個月的時間,對更新內容進行測驗;起算時間從測驗提供的起始日起算,例如:Winter'18 的發表日是 2017/10/16,但是更新測驗的起始日是 2017/11/20,往後約 8 個月,到 2018/7/13 是證照持有人需完成測驗的期限。

費用方面,目前基本的 Salesforce Developer Certification 考試費用是 USD $200,之後針對新內容的測驗費是 USD $100/year,如果同時擁有多張證照,每年維持有效的測驗費會有些可觀,但目前上限是 USD $300。

see also:
[1] Salesforce Blog: Seasonal Release
[2] Salesforce Certification: Maintenance Exam
[3] Salesforce: Certification Mainenence Overview
[4] Salesforce: Cost to Maintain a Salesforce Credential

2017-10-30

Salesforce: Objects Generalization and Specialization

Salesforce 在設定畫面上的 object 名詞可能是個讓習於物件導向程式開發者困惑的地方:畫面上的 object 代表 type definition,而非 instance,真正描述 instance 的名詞是 record。

然而在處理 generalization & specialization 議題時,畫面上又給了一個新名詞,record type。系統管理者可以把所有可能用到的欄位都開在同一個 object 裡,透過 record type 的方式,限定各個 record type 所能看到的欄位。值得注意的是:這邊的縮限手段,是採用綁定 layout 的方式達成,如果使用 Apex 程式存取,則不在此限。

從 generalization & specialization 的抽象層次來看,record type 確實提供了一種 specialization 的途徑,與物件導向中的 subclass 類似,但很不一樣的是:record type 處理的是資料面的 specialization,讓特定 record type 只能看到原來眾多欄位的子集合,而 subclass 同時兼顧資料面與行為面的 specialization,各個 subclasses 能夠自己額外定義欄位並實作不同行為。

此外,record type 並不像物件導向的繼承關係將共同的部分獨立抽取至 super class,相對於 record types,object 等於是所有欄位的大雜燴,看不到哪些是共同的欄位,對於 generalization 有潔癖的人來說,似乎只能用類似物件導向 composition 的方式處理 [1],也就是 Salesforce 中的 master-detail object relationships [2]。

使用 master-detail relationships 實現 generalization 難免有強加物件導向觀念的味道,但更不幸的是:即使採用這麼複雜的設定,仍然無法達成目的。原因在於:使用 master-detail relationships 實作 generalization 時,生命週期控制在 master 端,相當於是 specialized subclass;但在數量對應上 master 端屬於 one-side 而非 many-side,違背 generalization 的初衷。

see also:
[1] Salesforce Developers Forum: Custom Objects and Inheritance
[2] Salesforce Object Relationships

2017-10-24

Apex Serialize Objects to XML

先前的文章 [1] 中提到,Salesforce 的 REST 同時支援兩種輸出格式,XML 與 JSON;client 可以透過 HttpRequst header 中 Accept 屬性指定想要的 content type;這兩個 serialization 的過程對於 REST service 的實作者而言是完全 transparent 的,意即實作時 method 並不 return string 型別,而是直接 return sObject,由系統的 outbound messaging 機制直接處理掉。

然而對於 Apex 開發者而言,程式語言級別對 XML 與 JSON 在 serialization 的支援度就差很遠。以 JSON 為例,無論物件多複雜,要轉成 JSON string 只要一行 JSON.serialize() 就可以搞定 [2],但是轉成 XML string 時,無論使用 XmlStreamWriter [3] 或 DOM.XmlNode [4] [5] 都需要針對物件中各個 fields 作繁瑣地處理。

為了達到快速生成 XML 字串,我們可以透過 (1) 實作一 REST service;(2) 在 Apex 中呼叫該 service,並將 HttpRequest Accept header 設定為 'application/xml' 的方式,快速從 HttpResponse.getBody() 取得 outbound messaging 生成好的 XML 字串。

概念上是走 loopback invocation 的方式,利用既有的機制,寫最少的程式,完成任務。

see also:
[1] Salesforce RESTful Services: Response Content-Type
[2] JSON Support: Roundtrip Serialization and Deserialization
[3] XML Support: Writing XML Using Streams
[4] XML Support: Reading and Writing XML Using the DOM
[5] XmlNode Class
[6] Salesforce Trailblazer Community Idea: Convert Object to XML (Serialize)

.NET: Framework or Virtual Machine

一般提到 .NET 多半使用 .NET framewrok 這個名詞,內容可能包括:
  • Programming Languages: 包括 C#, C++/CLI, F#, IronLisp, IronPython, VB.NET, Powershell 等 [1]。
  • Compiler
  • CLI Standard Library: 裡頭主要有 Framework Class Library (FCL) [2] 與 Base Class Library (BCL) [3]
  • Common Intermediate Language (CIL)
  • Common Language Runtime (CLR)
對於傳統使用 C++ 搭配 MFC 開發視窗程式 (GUI desktop application) 的開發者而言,視線會集中在 C# 與 CLI Standard Library;此時 .NET 更趨近於 framework 的角色,提供眾多的 classes,開發人員根據 framework 規範撰寫 component。

然而就 .NET 整體架構來看,最大的改變在於提供了統一的 intermediate language (IL) 介面,使得前端可以同時支援多種程式語言,呼叫同一套 standard library;且這個 IL 設計是獨立於 CPU architecture 與 operating system platform 之外的 [4]。換言之,開發者可以自由地在任何想要的平台上開發 IL 所需的 runtime,因此角色上更接近 virtual machine。

雖然說大多數的 .NET 應用程式都在 Windows 與其內建的 runtime,但還是有不少開發者作過這方面的努力:
但實際使用上,卻不那麼順利。主因在於 .NET 雖然 CIL 提供了優良的隔離,獨立於 Windows 之外,但最重要的 CLI Standard Library 卻因為服務視窗程式開發者的初衷,納入了太多 Windows 特有的 library,尤其是圖形介面 (GDI) 這段,導致許多在 Windows 上開發的元件,無法順利過渡到其它 runtime 執行。

換言之,雖然 CIL 與 CLR 提供了很大 virtual machine 的特點,但身為 runtime infrastructure 重點的 standard library 並未提供相應的設計,導致整個 .NET 回到近乎 Windows 獨有的情況。

這幾年微軟開始改革 Windows Server 的產品線 [8],將圖形介面剝離出來,打破原本 Windows runtime 包山包海的狀況,同時也開始推更乾淨的 .NET Core;就架構而言這應該有助於第三方 .NET runtime 的普及,但微軟將發展 Mono 的 Xamarin 收入麾下卻讓非微軟勢力瞬間消失。

注意:Mono 在 runtime 實作仍是按照舊有的 CLI (ECMA-335) [9] 而非 CoreCLI,因此 Mono 是否會如 .NET Core 壯大仍有很大變數。

此外,Mono 的貢獻在不僅僅於 runtime,還有跨桌面平台的解決方案,這塊的未來應該屬於 Electron [10];至於依賴 Mono runtime 的跨行動平台方案 - Xamarin,個人認為疑慮就很大,目前 Windows Phone 已確定式微,微軟還願意花多少資源給跨行動平台開發方案,看來非常不樂觀。

這兩年微軟也開始引入 LLVM 技術,實作相關的 compiler (LLILC),想要深入這個主題的可以一起參考看看。

see also:
[1] Wikipedia: CLI Languages
[2] Wikipedia: Framework Class Library (FCL)
[3] Wikipedia: Standard Libraries (CLI) 
[4] Wikipedia: Common Intermediate Language (CIL)
[5] Wikipedia: DotGNU
[6] Wikipedia: Protable.NET
[7] Mono Project
[8] Wikipedia: Microsoft Windows Server Core
[9] ECMA-335: Common Language Infrastructure (CLI)
[10] Electron

2017-10-13

SSL Certificate Standards

要搞懂系統認證之前,首先要對非對稱式加密有基礎瞭解,不外乎就 2 種 key:
  • Public key: 用於加密與驗證簽章
  • Private key: 用於解密與產生簽章
至於這兩把 key 的產生通常會由共同信任的第三方,也就是 CA (Certificate Authority) 發出。[1]

Standards: Encoding Rules & Formats

接下來可能會遇到各種檔案,這裡頭牽涉的標準主要有 2 類:
  1. Encoding rule: 說明內容要如何編碼,主要有 DER 與 PEM [2] 兩種,前者的輸出為 binary,後者為 base64。
  2. Message or file format: 定義檔案應該包括那些資訊,主要有 PFX, P12 (PKCS #12), CSR (PKCS #10), X.509
  • PFX 或 P12 (PKCS #12) [3]: 也就是 CA-signed certificate;這類檔案帶有 private key,一般會被保護起來,很少複製傳遞。
  • CSR (PKCS #10) [4]: Certificate Signing Request; 為 client 向 CA 要求簽發憑證時使用,帶自身站台資訊,最少使用。
  • X.509 [6] 或 P7C (PKCS #7) [7]: 最常見,用於存放 public certificate 或 signature,其中 P7C 預定為 BER encoding,因此不會與其它 encoding rule 併用。
File Name Extensions

上頭這些標準名稱都常見於副檔名,譬如說:
  •  *.pfx 與 *.p12 一定是 CA-signed certificate,未指定 encoding rule,但通常為 binary,可與 PEM 合併使用。
  •  *.p7c 一定是 public certificate,固定為 BER encoding。
  •  *.der 與 *.pem 通常僅為 public certificate,用副檔名描述編碼方式。
除此,*.cer 與 *.crt 也是常見的副檔名,大多也僅為 public certificate,可套用任何 encoding rule。

最後是這些 key 如何被保存到 client 與 server;對系統來說,存放位置分為 TrustStore 或 KeyStore,分別存放扮演 consumer 與 provider 時,使用的 public certificate 與 private keys。
  • 與一般 symmetric authentication 所稱的 certificate 一詞不同,symmetric authentication 中的 certificate 多半為 username/password,但這邊的 certificate 泛指 signature 或包含 private key。
  • 例如:CA-signed certificate 即為兩者組合,而 public certificate 則為 signature。所以當單純看到副檔名 *.crt 或 *.cer,並無法確定內容是否包括 private key,類似的情況也發生在 *.pem 的檔案。

ps. Java 的 KeyStore 副檔名通常為 *.jks,由於 KeyStore 用來存放 CA-signed certificate 的關係,有些系統便使用 Java KeyStore 檔取代 *.pfx 或 *.p12,作為匯入 CA-signed certificate 時使用。


see also:
[1] Public Key Infrastructure
[2] PEM (Privacy-enhanced Electronic Mail): Base64 encoded DER certificate
[3] PKCS #12: Personal Information Exchange Syntax Standard
[4] CSR (Certificate Signing Request)
[5] PKCS (Public Key Cryptography Standards)
[6] X.509
[7] PKCS #7: Cryptographic Message Syntax
[8] DER vs. CRT vs. CER vs. PEM Certificates and How To Convert Them


Salesforce RESTful Services: Publish

當使用 Salesforce 作為 API provider 時,預設採用 OAuth 2.0 作為認證機制,如果想簡化認證過程,則可透過將 API 發佈到公開站台的方式完成,此時需要針對 site profile 進行設定。
  • 首先要進到 site details page;在 Setup / Build / Develop / Sites,點選 Site Label 欄位 (不是 Edit 也不是 Site URL)。
  • Site Details page 上方有個 Public Access Settings button,點進去後可能會因為 Org 不同而出現不同畫面:
  • Personal Develop Org: 畫面為一個很長的表格,要設定的地方有:Enabled Apex Class Access, Field-Level Security 與 Object Permissions。
  • Production Sandbox Org: 分成 Apps 與 System 兩段,需設定的連結落在 Apps 裡頭,分別是 Apex Class Access 與 Object Settings。
設定的重點不外乎:service class 本身以及相關需要存取的物件欄位,因此只要分別在 Apex Class 與 Objects 中將相關權限打開即可。

see also:
[1] Public RESTful Web Services on Force.com Sites
[2] Public Access Settings for Force.com Sites
[3] Public Access Settings in Salesforce