1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
# -*- coding: utf-8 -*-
require 'environment'
require 'fileutils'
require 'openssl'
require 'securerandom'
require 'json'
=begin rdoc
アカウントデータの永続化を行うユーティリティ。
このクラスは、他のプラグインからアクセスしないこと。
=end
module Plugin::World
module Keep
ACCOUNT_FILE = File.join(Environment::SETTINGDIR, 'core', 'token').freeze
ACCOUNT_TMP = (ACCOUNT_FILE + ".write").freeze
ACCOUNT_CRYPT_KEY_LEN = 16
extend Keep
@@service_lock = Monitor.new
def key
key = UserConfig[:account_crypt_key] ||= SecureRandom.random_bytes(ACCOUNT_CRYPT_KEY_LEN)
key[0, ACCOUNT_CRYPT_KEY_LEN] end
# 全てのアカウント情報をオブジェクトとして返す
# ==== Return
# account_id => {token: ,secret:, ...}
def accounts
if @account_data
@account_data
else
@@service_lock.synchronize do
@account_data ||=
if FileTest.exist? ACCOUNT_FILE
decrypted_string = decrypt(File.open(ACCOUNT_FILE, 'rb', &:read))
begin
::JSON.parse(decrypted_string, symbolize_names: true)
rescue ::JSON::ParserError
# 3.6.4以前はYAMLになっている。
# 構造自体には互換性があるため単純にJSONにコンバートする
d = account_write(YAML.load(decrypted_string))
notice 'Older account data was detected. It was converted newer format.'
d
end
else
migrate_older_account_data
end
end
end
end
# アカウント情報を返す
# ==== Args
# [name] アカウントのキー(Symbol)
# ==== Return
# アカウント情報(Hash)
def account_data(name)
accounts[name.to_sym] or raise ArgumentError, 'account data `#{name}\' does not exists.' end
# 新しいアカウントの情報を登録する
# ==== Args
# [name] アカウントのキー(Symbol)
# [options] アカウント情報(Hash)
# ==== Exceptions
# [Plugin::World::AlreadyExistError] _name_ のサービスが既に存在している場合
# [ArgumentError] _options_ の情報が足りない場合
# ==== Return
# self
def account_register(name, provider:, slug:, **options)
name = name.to_sym
@@service_lock.synchronize do
raise Plugin::World::AlreadyExistError, "account #{name} already exists." if accounts.has_key? name
@account_data = account_write(
accounts.merge(
name => options.merge(
provider: provider,
slug: slug)))
end
self
end
# アカウント情報を更新する
# ==== Args
# [name] アカウントのキー(Symbol)
# [options] アカウント情報(Hash)
# ==== Exceptions
# ArgumentError name のサービスが存在しない場合
# ==== Return
# self
def account_modify(name, options)
name = name.to_sym
@@service_lock.synchronize do
raise ArgumentError, "account `#{name}' does not exists." unless accounts.has_key? name
@account_data = account_write(
accounts.merge(
name => accounts[name].merge(options)))
end
self
end
# 垢消しの時間だ
# ==== Args
# [name]
# ==== Return
# self
def account_destroy(name)
name = name.to_sym
@@service_lock.synchronize do
to_delete = accounts.dup
to_delete.delete(name)
@account_data = account_write(to_delete) end
self end
# アカウント情報をファイルに保存する
def account_write(account_data = @account_data)
FileUtils.mkdir_p File.dirname(ACCOUNT_FILE)
File.open(ACCOUNT_TMP, 'wb'.freeze) do |file|
file << encrypt(::JSON.dump(account_data)) end
FileUtils.mv(ACCOUNT_TMP, ACCOUNT_FILE)
account_data end
def encrypt(str)
cipher = OpenSSL::Cipher.new('bf-ecb').encrypt
cipher.key_len = ACCOUNT_CRYPT_KEY_LEN
cipher.key = key
cipher.update(str) << cipher.final end
def decrypt(binary_data)
cipher = OpenSSL::Cipher.new('bf-ecb').decrypt
cipher.key = key
str = cipher.update(binary_data) << cipher.final
str.force_encoding(Encoding::UTF_8)
str end
private
def migrate_older_account_data
# 旧データの引き継ぎ
result = UserConfig[:accounts]
if result.is_a? Hash
# 0.3開発版のデータがある
account_write result.inject({}){ |hash, item|
key, value = item
hash[key] = value.merge(provider: :twitter, slug: key)
hash }
elsif UserConfig[:twitter_token] and UserConfig[:twitter_secret]
# 0.2.x以前のアカウント情報
account_write({ default: {
provider: :twitter,
slug: :default,
token: UserConfig[:twitter_token],
secret: UserConfig[:twitter_secret],
user: UserConfig[:verify_credentials] } })
else
{}
end
end
end
end
|