NaaN日記

やったこと、覚えたことを発信する場

GoogleのOAuth 2.0で受け取ったトークンをrevokeする

はじめに

GoogleのOAuth 2.0を使ったアプリケーションで、認証によって得たユーザの情報を用いて、条件に合ったユーザにのみ、サービスの利用を許可したい、という場合があります。

  • 例) 学生用メールアドレス(~ac.jp)を持つユーザにのみサービスを提供したい

このブログでは、この例の場合を想定して、話を進めていきます。

トークンを取り消すこと

Google OAuth 2.0では、認証された際に、アクセストークンが発行されます。このトークンを取り消す方法には、ユーザが自分で行う方法と、アプリケーション側で行う方法があります。

  1. ユーザがAccount Settingsにアクセスして、認証を取り消す
  2. アプリケーション側でリクエストを送信し、認証を取り消す

トークンをrevokeすることは、実際のところ、実装の必要性は低いかもしれません。今回は、以下の理由から、revokeを行います。

  1. サービスの利用を許可しないアカウントに対して、トーク*1を残しておく理由はない
  2. 認証後に、OAuth認証用のURL(例 : http://localhost/auth/login/google)へアクセスすると、Googleにログインしているアカウントが一つしかないとき*2、認証済みアカウントに対してトークンが再発行される。

2の場合について、例を思い出します。

例では、OAuth 2.0 を用いた認証後、認証に用いたGoogleのメールアドレスを確認し、条件に合わないユーザであれば、サービスの利用を許可せず、ユーザに通知します。

許可されなかったユーザのうち、学生用メールアドレスを持っているが、異なるアカウントでログインを試みてしまったというようなユーザは、学生アカウントで再認証を行います。

再認証を行うのですが、Googleにログイン中のアカウントが一つだけのとき、アカウントを追加し、切り替えることは少し面倒です。

OAuth 2.0 による認証後に、条件に合っているかどうかを判断しているということは、OAuth 2.0による認証は通っているということです*3

つまり、アプリケーションがサービスの利用を許可していない場合でも、認証を取り消さない間は、期限が切れるまで、認証されている状態になっています。

Google OAuth 2.0の仕様により、ログイン画面にアクセスすると、新しいアカウントを追加する間もなく、以前認証されたものは、自動で認証されます*4

この場合、ユーザが取る行動には、Googleのトップページからアカウントを追加するAccount Settingsから認証を取り消すなどがあります。


また、自動で認証が通るという仕様について、認可画面をスキップしないようにする方法があります。

OAuth 2.0のエンドポイント(https://accounts.google.com/o/oauth2/auth)に対し、
approval_prompt=force のクエリパラメータを付加することです。

しかし、この方法では、全ての(アカウントを一つしか持たない)ユーザが認可画面を毎回見る事になります。そのため、認可画面をスキップするか、しないか、どちらが良いかは状況によると思います。

次に、アプリケーション側でトークンを削除する方法について、書いていきます。

環境

  • Go 1.13.0
  • Echo 3.3.10 (Go web framework)
  • MySQL 8.0.17
  • Docker 19.03.5-ce
  • Docker Compose 1.25.0

認証には、Gomniauthを、データの処理のためにobjxを利用しています。

また、本ブログでは、説明に最低限必要な部分のみ記述しています。認証部分のソースコードは、Go言語によるWebアプリケーション開発を参考にしています。
www.oreilly.co.jp

トークンを取り消す方法

プログラムでトークンを取り消すには、パラメータにトークンを付加した、次の形式のリクエストを送信します。

https://oauth2.googleapis.com/revoke?token={token}

アクセストークンの取得

認証後に呼び出される関数CallbackHandler*5の中で、accessTokenを取得します。

/*
import(
	"net/http"

	"github.com/labstack/echo"
	"github.com/stretchr/gomniauth"
	"github.com/stretchr/objx"
)
*/

// CallbackHandler -- Provider called this handler after login
func (u *userHandler) CallbackHandler(c echo.Context) error {
	provider, err := gomniauth.Provider(c.Param("provider"))
	if err != nil {
		return err
	}

	omap, err := objx.FromURLQuery(c.QueryString())
	if err != nil {
		return err
	}

	creds, err := provider.CompleteAuth(omap)
	if err != nil {
		return err
	}

	accessToken := creds.Get("access_token").Str()

	return c.Redirect(http.StatusTemporaryRedirect, "/")
}

トークンを取り消す関数

取得したアクセストークンを引数に受けとる、以下の関数を作成しました。

/*
import (
	"io"
	"io/ioutil"
	"net/url"
	"net/http"
)
*/

func revokeToken(accessToken string) error {
	const googleRevokeURL = "https://accounts.google.com/o/oauth2/revoke"
	u, err := url.Parse(googleRevokeURL)
	if err != nil {
		return err
	}

	q := u.Query()
	q.Set("token", accessToken)
	u.RawQuery = q.Encode()
	resp, err := http.Get(u.String())
	if err != nil {
		return err
	}

	defer func() {
		io.Copy(ioutil.Discard, resp.Body)
		resp.Body.Close()
	}()

	return err
}

関数CallbackHandler内で取得したaccessTokenを、関数revokeTokenに渡すことで、トークンを取り消すことができました。
これにより、Googleにログインしているアカウントが一つであったとしても、認証用URLにアクセスすると、認可画面が表示されます。
また、条件に合わないユーザが認証してきたら、関数revokeTokenを呼び出す、というような利用もできます。
たとえ、認証後にログインページにリダイレクトするようなコードを書いてしまったとしても、無限ループにはならないと思います(そういう実装は良くないと思いますが……)。


ありがとうございました。

*1:有効期限はあるけれど

*2:アカウントが複数あるとき、認証するアカウントを選択する画面(認可画面)が表示される

*3:この言い回し、いつか話題になった構文のようですね

*4:認可画面をスキップしない方法はある

*5:詳しくは本を読むか、検索をお願いします

閉包テーブルに触れてみる

はじめに

これは、SLPアドベントカレンダー最終日の記事となっています。毎日異なる部員が執筆を行ってきました。私を含んだ、25人の記事をお楽しみください。

adventar.org


SQLアンチパターンを読み始めました。まだ読んでいる途中ですが、良い本です。
さて、この本の2章では、ナイーブツリーの話題が登場します。
そこでは、階層的なデータ設計が比較されます。

  • 隣接リスト
  • 再帰クエリ
  • 経路列挙
  • 入れ子集合
  • 閉包テーブル


今回、この中から閉包テーブルを選択して、コメント管理機能を作成しました。その中から部分的にプログラムを載せています。
閉包テーブルは、ツリー全体のパスを格納する方法です。特徴には、ノードが複数のツリーへ所属することが挙げられます。メインのテーブルとは別に、木構造のパスを記憶するテーブルを持ちます。

開発環境

  • ホストOS : Windows10 Pro 1909
  • ゲストOS : ArchLinux ( Kernel 4.19.88-1-lts x86_64)
  • Golang : 1.13.0
  • MySQL : 8.0.17
  • Echo 3.3.10 (Go web framework)
  • jmoiron/sqlx (database/sqlのラッパー)

テーブルの作成

CREATE TABLE comments (
    id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY,
    body TEXT NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP DEFAULT NULL,
    user_id INT NOT NULL REFERENCES user(id),
    question_id INT NOT NULL REFERENCES question(id)
);

CREATE TABLE tree_paths (
    ancestor BIGINT UNSIGNED NOT NULL REFERENCES comments(id),
    descendant BIGINT UNSIGNED NOT NULL REFERENCES comments(id),
    PRIMARY KEY (ancestor, descendant)
);
comments
属性* 概要*
id コメントのid。コメントを投稿すると、1から自動的に割り振られる
body コメントの内容
created_at コメントの作成時間
updated_at コメントの編集時間
deleted_at コメントの削除時間
user_id コメントを投稿した人のid
question_id 質問した話題のid
tree_paths
属性* 概要*
ancestor そのコメントの先祖となるコメントid
descendant そのコメントの子孫となるコメントid

ancestorとdescendantは、組み合わせが主キーとなっています。
自分自身を示すためには、両方の属性が同じidを持ちます。

コメントの登録

コメントを登録するには、2つの手順が必要です。

  1. コメントの挿入
  2. コメントのパスの挿入

プログラム内で、構造体は下のように定義しました。

type Comment struct {
	ID        uint64     `db:"id"`
	Body      string     `db:"body"`
	CreatedAt *time.Time `db:"created_at"`
	UpdatedAt *time.Time `db:"updated_at"`
	DeletedAt *time.Time `db:"deleted_at"`
	UID       uint64     `db:"user_id"`
	QID       uint64     `db:"question_id"`
}

/questions/:id へPOSTリクエストが行われると、ハンドラが呼び出されます。

func (comment *commentHandler) PostCommentHandler(c echo.Context) error {
        uid := getUserID()
	id := c.QueryParam("cid")
	body := c.FormValue("comment")
	param := c.Param("id")
	qid, err := strconv.ParseUint(param, 10, 64)
	if err != nil {
		return err
	}
	
	cid, err := strconv.ParseUint(id, 10, 64)
	if err != nil {
		return err
	}

	message := model.Comment{Body: body, UID: uid, QID: qid}
	fmt.Println(uid, qid, cid, body)
	err = comment.commentModel.CreateComment(&message, cid)
	if err != nil {
		return err
	}

	route := "/questions/" + param
	fmt.Println(route)
	return c.Redirect(http.StatusSeeOther, route)
}

コメントは、CreateCommentで作成されます。
CreateCommentの引数のparent_idは、コメントの親のIDです。
先祖を持たないコメントのとき、parent_idは0としています。
SELECT LAST_INSERT_ID()は、MySQL情報関数です。
最後にINSERTされたコメントのIDを取得するために用いています。

type CommentModel struct {
	db *sqlx.DB
}
// CreateComment -- Insert comment data
func (c *CommentModel) CreateComment(comment *model.Comment, parent_id uint64) error {
	var id uint64
	_, err := c.db.Exec(`INSERT INTO comments (body, user_id, question_id) VALUES (?, ?, ?)`, comment.Body, comment.UID, comment.QID)
	if err != nil {
		return err
	}
	err = c.db.Get(&id, "SELECT LAST_INSERT_ID()")
        if err != nil {
		return err
	}
	if parent_id == 0 {
		parent_id = id
	}
	_, err = c.db.Exec(`INSERT INTO tree_paths (ancestor, descendant) SELECT tree_paths.ancestor, ? FROM tree_paths WHERE tree_paths.descendant = ? UNION ALL SELECT ?, ?`, id, parent_id, id, id)
	return err
}

上のプログラムから、TreePathへのINSERT文を抽出しました。

INSERT INTO tree_paths (ancestor, descendant)
    SELECT tree_paths.ancestor, id FROM tree_paths
    WHERE tree_paths.descendant = parent_id
    UNION ALL SELECT id, id;

理解を深めるために、実際に挿入しながら考えていきます。


ここに、おすすめのテキストエディタを尋ねる質問があります。
CommentIDが7の人はVS Codeを、8の人はVimを勧めました。
ここで、8の人に対して学生R(CommentID: 9)がEmacsを勧めると、どうなるでしょうか。
図で書くと、このようになるはずです。

属性には、先祖となるコメントIDと、子孫となるコメントIDを持ちます。
そのため、tree_pathsには、(8, 9), (9. 9)の二つがINSERTされるはずです。

mysql> SELECT tree_paths.ancestor, 9 FROM tree_paths
    -> WHERE tree_paths.descendant = 8
    -> UNION ALL SELECT 9, 9;

+----------+---+
| ancestor | 9 |
+----------+---+
|        8 | 9 |
|        9 | 9 |
+----------+---+

> SELECT tree_paths.ancestor, 9
1列目にtree_paths.ancestor、2列目に9を射影
> FROM tree_paths WHERE tree_paths.descendant = 8
子孫のIDが、挿入するIDの親と等しいtree_pathsのテーブルを選択
> UNION ALL SELECT 9, 9;
自分自身を指す9, 9の行と結合

コメント情報の取得

質問に対しての全てのコメントをデータベースから取得するのなら、質問番号で選択すれば良いです。

func (c *CommentModel) All(question_id uint64) ([]model.Comment, error) {
	comments := []model.Comment{}
	err := c.db.Select(&comments, "SELECT * FROM comments WHERE question_id = ?", question_id)
	if err != nil {
		return nil, err
	}

	return comments, nil
}

次の関数では、指定されたコメントIDの子孫を全て取得します。tree_pathsで、指定されたコメントIDが先祖となっている行を選択します。

func (c *CommentModel) GetDescendant(id uint64) ([]model.Comment, error) {
	comments := []model.Comment{}
	err := c.db.Select(&comments, "SELECT comments.* FROM comments INNER JOIN tree_paths ON comments.id = tree_paths.descendant WHERE tree_paths.ancestor = ?", id)
	if err != nil {
		return nil, err
	}

	return comments, nil
}

先ほどのコメントに対し、学生Oと学生Sが参加しました。


学生Iの子孫を表示

学生Rの子孫を表示


おわりに

二つのテーブルを用いているため、どちらか一方のみを間違えないよう、一つのテーブルを用いるとき以上に気を使う必要があると感じました。コメントを登録するCreateComment関数では、データを二度INSERTします。しかし、現時点では、二番目のINSERTが失敗した際の、ロールバックを行う処理は未実装です。そのため、優先度高めで対応したいと思います。
また、tree_pathsに深さの属性を追加することで、直近の親子関係を簡潔に調べることができます。今回、その部分の設計は行わなかったので、今後、その部分の設計が必要かどうか、考えたいです。
今回、プログラムの全てをここに記述したわけではありません。またリファクタリングを行った際、記事にする予定です。

VMを使ってUSB経由でNFCを読み取る話

こんにちは。

現在、私の加入しているサークルでは、チーム開発の一つとして、
PaSoRiを使った出席管理システムを作成中です。

このシステムは、Python3で開発しています。
学生証をPaSoRiにタッチすると、サークルの活動に参加したことになり、その日の議事録に名前が載るシステムです(※開発中)。
NFCタグの読み取りのために、nfcpyのモジュールを使用しています。

現状、開発環境はWindows10となっています。
しかし、Windows10での開発は、正直言って面倒です。

今回、開発環境の構築までの手間を比較するために、
VirtualBox仮想マシンを作成し、PaSoRiを使用するための設定を行いました。

Windowsでの開発

Windowsで開発を行うためには、下の作業が必要です。

  • WinUSB(またはZadig)とlibusbの手動インストール
    • PaSoRiのドライバをWinUSBドライバに置き換える
    • libusbをインストールしたら、DLLファイルを指定されたディレクトリにCopyする
  • nfcpyをpipでインストール

今回、私のチームでは、Zadigとlibusbを手動インストールしました。
ダウンロードしてきたDLLファイルを、手動で C:\Windows\System32 や、C:\Windows\SysWOW64 にCopyすることは、置き間違えなどのリスクがあります。

VirtualBoxでの開発

  • Linuxでは、libusbは通常インストール済み
  • VM上でUSBデバイスを使うための設定を行う必要がある
  • 権限や、ドライバーの設定のために、少しコマンドを叩く必要がある
  • nfcpyをpipでインストール

Windows版と違い、手動でCopyするようなものはありません。
仮想マシンでUSBを認識するための設定や、一般ユーザが使用する際の権限の設定などをする必要があります。

環境

今回、動作確認のために用意したものは以下の通りです。

今回、新しいものが使いたいという理由と、約一年前に友人から布教を受けたこともあり、ArchLinuxを使いましたが、Ubuntuなどでも動くと思います。

設定の流れ

設定の流れは、次のようになります。

  1. 仮想マシンの用意
  2. VirtualBox仮想マシンの設定でUSBの設定
  3. PaSoRiの認識確認
  4. nfcpyのインストール
  5. PaSoRiの接続設定

ここでは、2以降の手順を記述します。

VirtualBox仮想マシンのUSB設定

まず、手元のVirtualBoxに設定済みの仮想マシンを用意します。
そして、仮想マシンの状態が電源オフであることを確認します。次に、PaSoRiをUSBポートに挿します。

仮想マシンのUSBデバイスフィルターの設定
VirtualBox仮想マシンに対するUSBデバイスフィルターの設定

使用する仮想マシンの設定から、USBを選択、USB2.0コントローラーを有効にします。次に、右にある新規のUSBフィルターの追加から、目的の物(今回の場合はSONY RC-S380/P)を選択します。

PaSoRiを認識しているかの確認

次に、仮想マシンを立ち上げ、PaSoRiが認識されているかを確認します。認識を確認する方法はいくつかありますが、ここでは二つ紹介します。

1. lsusbコマンドを使用

$ lsusb
Bus 002 Device 003: ID 054c:06c3 Sony Corp. RC-S380

このように表示されたら認識されています。

2. dmesgコマンドを使用

$ dmesg | grep SONY # または grep NFC

それっぽいものが表示されたら、認識されています。

nfcpyのインストール

venvを使用し、仮想環境内にインストールします。

venvを使用する理由

venvとは、軽量な仮想環境の作成をサポートしてくれるものです。Python3の標準の機能の一つです。

メリット
  • 仮想環境の中に、パッケージ群を独立してインストールできる
    • 仮想環境ごとに、使用するパッケージ群のバージョンを分けられる
  • それぞれの仮想環境は独立しているため、仮想環境間では競合しない

nfcpyは、現在のプロジェクトでのみ使用し、頻繁に使用するものではないため、仮想環境の中で使用します。

nfcpyのインストールの流れ

$ python -V
Python 3.8.0
$ cd [WORKDIR] # 作業ディレクトリへ移動
$ python -m venv [ENV_DIR] # 環境の作成 (ENV_DIRをvenvとする例が多いらしい)
$ source [ENV_DIR]/bin/activate
(venv)$ pip install nfcpy
(venv)$ pip freeze # インストールしたパッケージの確認

PaSoRiの接続設定

接続確認のために、下のコマンドを実行します。

(venv)$ python -m nfc

いくつかエラーが出ることがあるので、確認したものについて順に対処していきます。

This is the 1.0.3 version of nfcpy run in Python 3.8.0
on Linux-5.4.2-arch1-1-x86_64-with-glibc2.2.5
I'm now searching your system for contactless devices
** found usb:054c:06c3 at usb:002:004 but access is denied
-- the device is owned by 'root' but you are 'amakuchi'
-- also members of the 'root' group would be permitted
-- you could use 'sudo' but this is not recommended
-- it's better to adjust the device permissions
   sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", MODE=\"0666\" >> /etc/udev/rules.d/nfcdev.rules'
   sudo udevadm control -R # then re-attach device
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs
Sorry, but I couldn't find any contactless device

このように出ることがあります。
要するに、デバイスを "plugdev" というグループへ追加することを推奨しています。
言われた通りに作業を行います。

$ sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", MODE=\"0666\" >> /etc/udev/rules.d/nfcdev.rules'

$ sudo udevadm control -R

PaSoRiの再接続を行い、もう一度、実行します。

(venv)$ python -m nfc
This is the 1.0.3 version of nfcpy run in Python 3.8.0
on Linux-5.4.2-arch1-1-x86_64-with-glibc2.2.5
I'm now searching your system for contactless devices
** found usb:054c:06c3 at usb:002:005 but it's already used
-- scan sysfs entry at '/sys/bus/usb/devices/2-2:1.0/'
-- the device is used by the 'port100' kernel driver
-- this kernel driver belongs to the linux nfc subsystem
-- you can remove it to free the device for this session
   sudo modprobe -r port100
-- and blacklist the driver to prevent loading next time
   sudo sh -c 'echo blacklist port100 >> /etc/modprobe.d/blacklist-nfc.conf'
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs
Sorry, but I couldn't find any contactless device

今度は、デバイスのport100は、既に使われているというメッセージが出ました。

そのため、port100のカーネルドライバを取り外します。

$ sudo modprobe -r port100

また、今後も停止させ続けておきたい場合は、ブラックリストに入れます。

$ sudo sh -c 'echo blacklist port100 >> /etc/modprobe.d/blacklist-nfc.conf'

もう一度、実行します。

(venv)$ python -m nfc
This is the 1.0.3 version of nfcpy run in Python 3.8.0
on Linux-4.19.88-1-lts-x86_64-with-glibc2.2.5
I'm now searching your system for contactless devices
** found SONY RC-S380/P NFC Port-100 v1.11 at usb:002:003
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs

このように出たら、成功です!最後に、きちんと終了させます。

(venv)$ deactivate # 仮想環境から出る

おわり!

仮想マシンのUSBデバイスフィルターの設定は、GUIでしましたが、操作ミスの起こりにくい場所であり、ファイルの手動コピーよりは心理的安全性がありました。また、仮想マシン内での操作は、コマンドをぽちぽち貼り付けるだけなので、個人的には、Windows版でやるより、仮想マシンの中でやるほうが楽だと感じました。
それから、やはりWindowsの方のドライバはあまり触りたくないなーという気持ちがあります。libusbの導入と、Zadigのインストールをしなくて良いというだけで、仮想マシンでの開発を選ぶメリットがある気がします。

なお、今回一番時間がかかったのは、ArchLinuxの設定です……。しばらく環境を壊さないように気を付けたいです……。また、ArchLinuxのデスクトップ環境は作っていません。Webアプリケーション以外のGUIアプリケーションを作る予定がない間は、デスクトップ環境は作らないと思います。作ったらまたブログに書くと思います。
それから、昨年の誕生日プレゼントにPaSoRiをくださった先輩、ありがとうございました☺

WLSのUbuntu16.04を18.04にした話

はじめに

ずっと、WSLでUbuntu16.04を使っていたので、そろそろ18.04にしようという話。
今後、後輩に18.04を入れさせて、自分は16.04というようなことになったら困るので……。

バージョン確認 (1)

amakuchi@curry:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.4 LTS
Release:        16.04
Codename:       xenial

現在、16.04であることを確認。

lxdとlxd-clientのパッケージを削除する

WSLでは、systemdがなく、snapによるインストールができない。
そのため、この作業を行わずに do-release-upgrade を行うと、lxdに関連する場所で、タイムアウトまで約10分間待つことになる。

sudo apt purge lxd && sudo apt purge lxd-client

更新

sudo apt update && sudo apt dist-upgrade

/etc/update-manager/release-upgrades の中で、
Prompt=lts
となっていることを確認する。

amakuchi@curry:~$ cat /etc/update-manager/release-upgrades
# Default behavior for the release upgrader.

[DEFAULT]
# Default prompting behavior, valid options:
#
#  never  - Never check for a new release.
#  normal - Check to see if a new release is available.  If more than one new
#           release is found, the release upgrader will attempt to upgrade to
#           the release that immediately succeeds the currently-running
#           release.
#  lts    - Check to see if a new LTS release is available.  The upgrader
#           will attempt to upgrade to the first LTS release available after
#           the currently-running one.  Note that this option should not be
#           used if the currently-running release is not itself an LTS
#           release, since in that case the upgrader won't be able to
#           determine if a newer release is available.
Prompt=lts

ここで一旦WSLを閉じます。

バージョン確認 (2)

amakuchi@curry:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.6 LTS
Release:        16.04
Codename:       xenial

少し新しくなりました。

do-release-upgradeの確認

amakuchi@curry:~$ do-release-upgrade -c
Checking for a new Ubuntu release
New release '18.04.2 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

'18.04.2' にupgradeできることがわかる。

do-release-upgradeを行う

amakuchi@curry:~$ sudo do-release-upgrade

一回Enterキーを押した後、適所でyを押せばOK

暫く待つと、次のような表示になる。

f:id:CNaan:20190423181048p:plain
ssh-server として使用する予定はないので、
install the package maintainer's version を選択。

引き続き作業が行われる。

※ ここで、lxdのパッケージを削除していなかったら、タイムアウトまで待つことになる

最後に、Restartをするかを聞かれるが、yを押してもWSLの再起動はされない。

その後、Enterキーを押して、

Press x to destroy or r to resurrect window

と出るので、xを押して閉じる。

amakuchi@curry:~$ exit

アップグレードが完了したことの確認

amakuchi@curry:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.2 LTS
Release:        18.04
Codename:       bionic

できた

MESHを触った話

はじめに

MESH:小さな便利を形にできる、ブロック形状の電子ブロック|ソニー をつかいました。

MESHには、温度、光などのセンサーや、スイッチなどのブロックがあります。
センサーはMicro Bで充電します。

今回はApp StoreからMESHをインストールし、人感センサーを用いました。

やってみる

人感センサーが反応したら、Slackにタイムスタンプを送ります。
まずは、人感センサーブロックをiPadとペアリングします。

右上の追加ボタンから追加。人感センサーを長押しすることで、センサーの電源が入ります。

ペアリングできたら、ブロックを右から移動してマップに配置します。

配置したslackアイコンをタップします。
データを追加から、タイムスタンプを選択しました。

スクロールすると、IFTTTアプレット設定があります。IFTTTの設定をするように求められます。
MESHとIFTTTにメールアドレスを登録し、パスワードを設定します。

ワークスペースを決め、設定したら、チャンネルを選んだりします。

人感センサーが反応すると、タイムスタンプが送信されました。


写真を撮ってみる

センサーが動きを検知したら、現場の写真を撮っておきたいと思いました。監視カメラっぽくて楽しそう。

画像はちゃんとiPadに保存されました。
先日サークルの活動場所の入り口に人感センサーブロックを置いておいたところ、100枚以上の写真が集まりました(消しました)。

今後の話

RaspberryPi使ってカメラを独立させたい。
このリポジトリでやる予定です。
github.com

slackのWebAPIを使ってみた話【トークン入手編】

メモについて

みなさん、思いついたこととか、忘れないでおきたいことがあるとき、どこにメモしますか?
私は、よくSlackにメモします。理由としては、PC、iPadスマートフォンどれからでも確認ができるからというのが大きいです。
しかし、Slackというのは沢山ワークスペースや、チャンネルがありますよね。間違えそうになること、無いですか。
一回だけ、自分以外の人がいるgeneralにメモをするということをやりました。夜中にぼーっとするのは危ないです。

自分だけがいるワークスペースがあったとしても、間違えない可能性ってゼロじゃないですよね。私はそうです。というわけで、Slackを使わずにSlackにメモをすれば、適切な場所にメモを残すことができるのでは?と考えました。
それから、毎回メモしたいときにSlackを開くのも手間なので、もっと手軽にメモできるようにしたいですね。夕食にナンが食べたいなあと思ったとき、"ナン"の2文字をメモするためにSlackをアクティブにして、送信するのは、ちょっと大変です。

WebAPIを使おう

メッセージをチャンネルに送信することが目的なので、WebAPIを使用します。
Slack API 推奨Tokenについて - Qiita
こちらに沿ってアプリを作成し、トークンを発行します。

  1. https://api.slack.com/appsにアクセスし、右上のCreate New Appを選択
  2. アプリの名前と、使用するワークスペース名を選択
  3. permission scopeの設定(今回は、Send message as {アプリ名}を選択)
  4. 上にある緑のボタン[Install App to Team]からInstallを行う
  5. Authorizeすると、トークンが表示される

permission scopeが見つからない場合は、OAuth & Permissionsの項目を確認すると良い

今回は、チャンネルにメッセージを送信できれば良いので、
chat.postMessage method | Slack
を用います。推奨メソッドはPOSTです。

https://api.slack.com/methods/chat.postMessage/test
こちらで、ボタン[Test Method]を押すと、URLの見本を作ることができます。

token: xxxx-xxxx-xxxx-xxxx
channel: everyone
text: Hello

を入力したとすると、このように出力されます。

https://slack.com/api/chat.postMessage?token=xxxx-xxxx-xxxx-xxxx&channel=everyone&text=Hello&pretty=1

実際に自分の持っているトークン、チャンネル、メッセージを入力すると、送信されたことが確認できると思います。

curlで送信したいとき

 curl -XPOST -d 'token={token}&channel={channel}&text={text}' https://slack.com/api/chat.postMessage

おわりに

そもそもメモする場所を変えれば済むのではと少しだけ思った

参考
Slack APIを使用してメッセージを送信する - Qiita

初めて学生LTに参加した話

はじめに

お久しぶりです。最近サボタージュしてました甘口です。アウトプットもかなりお休みして、のんびりしていました。今日は新元号が発表されるんですかね。ああ、エイプリルフールのネタ考えてないです。
何かするでもなく、フラフラしてたら丁度良いタイミングで、近場で学生LT(in 愛媛)を見つけたので、これだと思い参加してきました。

何枠で応募するか

最所の関門ですね。応募枠には、LT枠と参加枠がありました。
やっぱり、せっかくならLTしてみようと、もちろんLT枠で応募を……というわけではなく、最初は参加枠で応募しました。その後、話す内容も思い浮かんだので、LT枠にひっそりと変更しました。

LTの内容

簡単な自己紹介を行った後に、Pyxel(ピクセル)というPython向けのレトロゲームエンジンの紹介をしました。Pyxel面白いです。私の制作物の話はそのうち記事にまとめる予定です。

Pyxelの特徴

  • 使える色は16色のみ
  • 同時に再生できる音は4音まで
  • レトロゲーム機を意識したシンプルな仕様

個人的にPyxelで面白いと感じているのは、タイルマップエディタという、マップをタイルで配置することができるところです。
f:id:CNaan:20190401000727p:plain
(右側のイメージバンクの画像を左に配置できる)

懇親会

f:id:CNaan:20190331235603j:plain

おいしかったです。

今後

楽しかったので、また参加したいなと思いました。
とある大学では、2年次にRaspberryPiとアセンブリ言語を使った授業があるという情報を入手したので、今年度はその辺やってみたいですね。