ファストユーザースイッチの快適さを追求する

コマンド環境においては、ユーザーの切り替えは非常に簡単である。

#aliceの操作環境
MacBook:/ alice$ su bob
Password:

#bobの操作環境
MacBook:/ bob$ exit
exit

#aliceの操作環境
MacBook:/ alice$
  • su bobで、bobユーザーに切り替え。
  • exitで、bobユーザーをログアウトして、aliceユーザーに戻る。

コスト最小限で素早く切り替えられる。快適!


一方、OSXGUI環境にもファストユーザースイッチがある。

  • システム環境設定 >> アカウント >> ログインオプション >> 「ファストユーザースイッチメニューを表示」をチェック入、で有効になる。


ファストユーザースイッチで切り替えれば、現状のGUIな作業状態を保持したまま、他のユーザーでログインできる。デスクトップを立方体の一面と見立てて、回転しながら切り替わる様子は、見ていて楽しい。

でも、見ていて楽しいのは最初のうちだけ。すぐに切り替え操作が面倒になる。マウスでメニューバー右側をクリックして、ユーザーを選択して、パスワードを入力する。たった3つの手順なんだけど、何度も繰り返していると面倒になる。

そもそもマウス操作が面倒くさい。でも、アイコンメニューにはショートカットが割り当てられない。その不便を解消するのにやってみたのが以前のこの日記。

あれから数年経ち、別の作戦も思い浮かんだので再び試行錯誤してみた。

ショートカットもどき

Spotlightと違って、ファストユーザースイッチに直接ショートカットは設定できない。でも強いて言えば、control-F8、←、↓、でショートカットもどきのことはできる。しかし、とても瞬時に押せる組み合せではないので、あまりお勧めはできない。

  • システム環境設定 >> キーボード >> キーボードショートカット >> キーボードと文字入力 >> 「ステータスメニューを操作対象にする」チェック入 の設定必要


ショートカットで起動する

ところで、ファストユーザースイッチはステータスメニューであることは分かった。ステータスメニューは、AppleScriptで操作することはできる。そのAppleScriptQuicksilverなどのキーボードランチャーでショートカットを割り当てておけば、ファストユーザースイッチを素早く起動できるのだ。そのAppleScriptがこれ。


tell application "System Events"
tell process "SystemUIServer"
click (menu bar 1's menu bar item 1 whose description is "user") end tell
end tell

  • システム環境設定 >> ユニバーサルアクセス >> 「補助装置にアクセスできるようにする」チェック入 の設定必要


メニュー操作をする

GUIスクリプティングをもう少し頑張れば、メニューを自由に操作できる。ユーザー名を選択して、パスワード入力することだって可能。


click_menu_extra({"user", "alice"}) input_password("XXXX")
on click_menu_extra(menu_path) set mp to menu_path as list
tell application "System Events"
tell process "SystemUIServer"
click (menu bar 1's first menu bar item whose attribute "AXDescription"'s value is (mp's item 1)) repeat with i from 2 to mp's length
click (result's menu 1's menu item (my number_from(mp's item i))) --クリック
--select (result's menu 1's menu item (my number_from(mp's item i)))--選択のみ
end repeat
delay 0.2 --連続して操作する時、ひと呼吸必要
end tell
end tell
end click_menu_extra

on number_from(str) try
str as number
on error
str
end try
end number_from

on input_password(str) tell application "System Events"
tell process "SecurityAgent"
delay 0.5 --連続して操作する時、ひと呼吸必要
keystroke str & return
end tell
end tell
end input_password

その他のメニューエクストラを調べる

上記の方法を使えば、その他のメニューエクストラにも素早く、確実にアクセスできる。でも、アクセスするには、メニューエクストラ名を調べておく必要がある。それは以下のコードで一覧できる。


tell application "System Events"
tell process "SystemUIServer"
set frontmost to true
menu bar 1's menu bar items's description
--or--
--menu bar 1's menu bar items's properties
end tell
end tell

{"キーチェーン menu extra", "remote desktop", "menu extra", "iStat menus Temperatures", "iStat menus Network", "iStat menus Memory", "iStat menus CPU", "time machine", "Sync", "AppleScript", "bluetooth", "システムサウンド音量", "AirMac Menu Extra", "text input", "バッテリーメニュー。 充電済み .", "user"}

コマンドでアクセスする

AppleScript、特にGUIスクリプティングは冗長である。ファストユーザースイッチをクリックするだけでも、上記のように多くのコードが必要なのだ...。
ところが、コマンドを利用すればたった1行で済む。

$ /System/Library/CoreServices/'Menu Extras'/User.menu/Contents/Resources/CGSession -switchToUserID `id -u 'alice'`

コマンドの素晴らしいところは、環境に依存しないところ。「補助装置にアクセスできるようにする」チェック無しでも、さらには、「ファストユーザスイッチメニューを表示」チェック無しでも、確実にファストユーザースイッチが起動できるのだ。

AppleScriptに組み込めば、先ほどまでのコードは、こんなにシンプルになる。


fast_user_switch("alice") input_password("XXXX")
on fast_user_switch(user_name) do shell script "/System/Library/CoreServices/'Menu Extras'/User.menu/Contents/Resources/CGSession -switchToUserID `id -u " & quoted form of user_name & "`"
end fast_user_switch

on input_password(str) tell application "System Events"
tell process "SecurityAgent"
delay 0.5 --連続して操作する時、ひと呼吸必要
keystroke str & return
end tell
end tell
end input_password

セキュリティを考える

自動的にファストユーザースイッチが実行できるようになったが、たった1つ大問題がある。それは、ログインパスワードがコードの中に見えていること。

ちょっとぐらいいいじゃないか、という考え方もある。しかし、今時のOSXでは、ログインパスワードは最高機密なのだ。それさえ分かれば、何でもできてしまう。キーチェーンに登録された各種パスワードも、デフォルトではログインパスワードがマスターキーになっている。そのログインパスワードが文字として見えてしまっていることに、危機感を感じる。(感じなければならないのだ!)

そこで、以下のような対策を考えた。

  • 実行専用で保存
  • プロパティとして保存
    • アプリケーション形式のAppleScriptは、プロパティ変数の内容をスクリプト終了後も永続的に保存する。


これらの対策は有効か?試してみた。

    • 実行専用で保存すれば、確かにAppleScriptエディタでは開けなくなる。
    • ところが、テキストエディタで開いて内容を見ることは簡単にできた。
    • その内容は、AppleScriptとしてコンパイルされたバイナリデータである。
    • コードの内容は意味不明。
    • しかし、パスワードとして入力した「文字列の並び」は、比較的簡単に判別できてしまう。


set pass to "ABCDEFG"

    • コードに含めず、プロパティとして保存した場合も同様であった。


property pass : ""
display dialog "入力してください。" default answer ""
set pass to result's text returned

いずれの方法も、全く対策にはならない...。(passの内容「ABCDEFG」が丸見え)


以上の結果から...

  • ログインパスワードを永続的に保持することは、諦めた。


方針は決まった。

  • 「実行後、自動的に終了しない」アプリケーション形式で保存して、
  • スクリプトのアプリケーション起動毎に、ユーザー名とパスワードを入力することにした。
  • スクリプトのアプリケーション終了まで、ユーザー名とパスワードを保持する。
  • そして、2回目以降の起動操作では自動的にファストユーザースイッチを実行する。


出来上がったAppleScript

  • faster_user_switching.app


global userName
global userPass

on run
set userName to ""
set userPass to ""
main() end run

on reopen
main() end reopen




on main() save_user_name() save_user_pass() fast_user_switch() input_login_password() end main

--ユーザー名の登録(ユーザー名を変更したい時は、このスクリプト起動直後にdeleteキーを押す)
on save_user_name() if userName = "" then
set msg to "ユーザー名を入力してください。"
set icon_name to note
repeat
activate
display dialog msg default answer userName with icon icon_name
set userName to text returned of result
try
user_id() exit repeat
on error
if userName is not "" then
set msg to "このユーザーは登録されていません。\nもう一度、入力してください。"
set icon_name to caution
end if
end try
end repeat
end if
end save_user_name

--パスワードの登録
on save_user_pass() repeat while userPass is ""
activate
display dialog "パスワードを入力してください。" default answer "" with icon note with hidden answer
set userPass to text returned of result
end repeat
end save_user_pass

--ユーザー名からユーザーIDを取得
on user_id() do shell script "/usr/bin/id -u " & quoted form of userName
end user_id

--ファストユーザースイッチを実行
on fast_user_switch() do shell script "/System/Library/CoreServices/Menu\\ Extras/User.menu/Contents/Resources/CGSession -switchToUserID " & user_id() end fast_user_switch

--ログインパスワードの入力処理
on input_login_password() tell application "System Events"
repeat 20 times
delay 0.1
if exists window 1 of application process "SecurityAgent" then exit repeat
end repeat
if userPass is not "" then
tell process "SecurityAgent"
keystroke userPass & return
delay 1
keystroke tab using {command down} end tell
end if
end tell
end input_login_password

シンプルだったAppleScriptも、セキュリティとユーザーインターフェースを考えると、長いコードになってしまうのであった。

多数のユーザーを扱う

手軽に、素早く、ファストユーザースイッチできるようになると、調子に乗って多数のユーザーでログインし始める。2ユーザーの切り替えなら、上記のAppleScriptで快適に使える。しかし、3ユーザー、4ユーザーになると、スクリプトをコピーして複数のfaster_user_switching.appを起動する必要がある。その都度、ショートカットを各ユーザーに追加設定するのも面倒である。

そこで、一つのスクリプトで複数ユーザーを管理できるように修正してみた。ユーザーを選択するという操作が追加になるので、ショートカット一発でファストユーザースイッチを起動できなくなるが、3ユーザー以上を切り替えて運用したい環境では、この方式の方が扱い易いかもしれない。

  • select_user_switching.app


global userName
global userPass
global NEW_ITEM
global userList
global passList

on run
set userName to ""
set userPass to ""
set NEW_ITEM to "--追加・修正--"
set userList to {} set passList to {} main() end run

on reopen
main() end reopen




on main() select_user() fast_user_switch() input_login_password() end main

--ユーザーリストの表示
on select_user() if userList = {} then
set select_name to NEW_ITEM
set userName to ""
else
if userName is not in userList then set userName to userList's item 1
set res to choose from list userList & NEW_ITEM with title "ユーザースイッチ" default items userName --OK button name "切替"
if res = false then
error
else
set select_name to res's item 1
end if
end if
if select_name = NEW_ITEM then
save_user_name() save_user_pass() else
repeat with name_pass in passList
if select_name = name_pass's item 1 then
set userName to name_pass's item 1
set userPass to name_pass's item 2
exit repeat
end if
end repeat
end if
end select_user

--ユーザー名の登録
on save_user_name() set msg to "ユーザー名を入力してください。"
set icon_name to note
repeat
activate
set res to display dialog msg default answer userName ¬ buttons {"キャンセル", "削除", "OK"} ¬ default button {"OK"} ¬ with icon icon_name
set userName to res's text returned
set MODE to res's button returned
try
user_id() exit repeat
on error
if userName is not "" then
set msg to "このユーザーは登録されていません。\nもう一度、入力してください。"
set icon_name to caution
end if
end try
end repeat
if MODE = "OK" then
add_user_list() else
delete_user_list() error --以降の処理を中止するため
end if
end save_user_name

--ユーザーリストに追加する(既存のユーザーには上書き)
on add_user_list() if userName is in userList then
return
else
set userList's end to userName
end if
end add_user_list

--ユーザーリストから削除する
on delete_user_list() if userName is in userList then
set res to {} repeat with user in userList
if user's contents is not userName then set res's end to user's contents
end repeat
set userList to res
end if
end delete_user_list

--パスワードの登録
on save_user_pass() activate
display dialog "パスワードを入力してください。" default answer "" with icon note with hidden answer
set userPass to text returned of result
add_pass_list() end save_user_pass

--パスワードリストに追加する(既存のユーザーには上書き)
on add_pass_list() repeat with name_pass in passList
if name_pass's item 1 = userName then
set name_pass's item 2 to userPass
return
end if
end repeat
set passList's end to {userName, userPass} end add_pass_list

--ファストユーザースイッチを実行
on fast_user_switch() do shell script "/System/Library/CoreServices/'Menu Extras'/User.menu/Contents/Resources/CGSession -switchToUserID " & user_id() end fast_user_switch

--ユーザー名からユーザーIDを取得
on user_id() do shell script "/usr/bin/id -u " & quoted form of userName
end user_id

--ログインパスワードの入力処理
on input_login_password() tell application "System Events"
repeat 20 times
delay 0.1
if exists window 1 of application process "SecurityAgent" then exit repeat
end repeat
if userPass is not "" then
tell process "SecurityAgent"
keystroke userPass & return
end tell
end if
--パスワードが違った場合は、パスワード入力フィールドにフォーカスしたままにする
delay 1
if not (exists window 1 of application process "SecurityAgent") then
keystroke tab using {command down} end if
end tell
end input_login_password

さらなる展望

  • faster_user_switching.app
  • select_user_switching.app

どちらのアプリも、初回起動時にはユーザー名・パスワードを入力する必要がある。セキュリティを考慮した結果、このような仕様としたのだが、頻繁に使い出すと初回のユーザー名・パスワード入力さえも、面倒に感じ始める。どうにかならないのか?→

キーチェンアクセス
  • OSX標準のキーチェーンアクセスを利用すれば、パスワードを安全に管理できるはず。
  • ファストユーザースイッチ用のキーチェーンを追加して、そこでパスワードを管理するようにすれば、多少は便利になるかもしれない。
  • 但し、初回にキーチェーンを解除するパスワードを入力する手間は必要になる。
  • それでも、3ユーザー以上を管理するselect_user_switching.appなら便利かもしれない。
暗号化してプロパティ保存&読取専用
    • 入力したプロパティを暗号化する。
    • かつ読取専用のAppleScriptにしておく。
  • 以上の合わせ技を使えば、暗号化の過程は難読化されるので、簡単にはパスワードを解読できなくなるかもしれない。
  • ところが、そもそも自分は読取専用のAppleScriptが嫌いである。
  • 過去、読取専用で保存してしまって、丸一日かかって作ったAppleScriptコードが復元できなくなってしまった、という苦い経験もある。
  • せめて作成者ぐらいは、パスワード入力によってコードを復元できるようにして欲しいものだ。
  • それから、すべてのAppleScriptコードがオープンになっていて欲しい。
  • 自分以外の誰かが作ったコードの中には、素晴らしい技がたくさん隠れている。
  • それらが見られなくなってしまうのは、とても悲しい。
  • キーチェンアクセスは安全。
    • 但し、キーチェーンを解除する手間が必要。
  • 暗号化&読取専用なら一切の手間は不要になる。
    • しかし、コードが見えなくなるのが嫌い。
    • 本当に安全が確保されるのかも未知。

どちらを選択すべきか、悩ましい。