Gnome Keyringで一度に複数のアカウントのパスワードを変更する

Gnome Keyringで一度に複数のアカウントのパスワードを変更する

私はさまざまなアプリケーションのパスワード保存バックエンドとしてGnome Keyringを使用しています。多くの項目は、異なる方法でアクセスされる同じアカウントにログインするさまざまな方法についてのものです。このアカウントのパスワードを変更したので、キーリングのすべてのアイテムを更新したいと思います。

私は通常Seahorseを使用してキーリングを編集しますが、少し厄介なキーストロークまたはマウスクリックでのみ個々のアイテムを編集できます。変更が必要なパスワードが多いため、これは退屈な作業です。

Gnome Keyringの多くの項目のパスワードを効率的に更新する方法、つまりパスワードを繰り返し入力する必要なく、どうすればよいですか。

ベストアンサー1

次のようなうーん提案特定の基準(特定のユーザー名やサーバー名など)に一致するすべてのエントリのパスワードを変更するPythonスクリプトを作成しました。スクリプトは完全な確認のために古いパスワードを要求し、指定された古いパスワードを持つエントリのみを変更します。使用例:

keyring-change-passwords 'user|username_value=^gilles$' 'action_url|server=acme\.example\.com'

警告:コードが一度満足して実行されました。これが私のテストの範囲です。

注:APIは時間の経過とともに変わります。以下のコードはUbuntu 20.04用です。一つこの回答の以前のバージョンUbuntu 14.04で実行されるコードがあります。

#!/usr/bin/env python3

"""Change multiple entries in the Gnome Keyring login keyring.

Prompt for the old and new password. Only entries for which the old password
matches are modified.

Condition syntax:
  ATTRIBUTE[,ATTRIBUTE...]=REGEX
e.g.
  bar,baz=^foo
Only match if the "bar" attribute starts with "foo". If there's no "bar"
attribute, use "baz" instead.
"""

import argparse
import getpass
import os
import re
import sys
import time

import keyring

def print_info():
    cfg = keyring.util.platform_.config_root() + '/keyringrc.cfg'
    print("Using keyring configuration file:", cfg)
    if os.path.exists(cfg):
        print(re.sub(r'^', r'  ', re.M), open(cfg).read())
    print("Any data files are in:", keyring.util.platform_.data_root())
    kr = keyring.get_keyring()
    print("Backend name:", kr.name)
    if hasattr(kr, 'backends'):
        print("Backends:")
        for b in kr.backends:
            print('{}; priority={}, viable={}'
                  .format(b.name, b.priority, b.viable))

def getpass2(prompt):
    input1 = getpass.getpass(prompt)
    input2 = getpass.getpass("Repeat " + prompt)
    if input1 != input2:
        raise ValueError("password mismatch")
    return input1

def format_date(seconds):
    return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(seconds))

def maybe_add_attribute(item, attributes, name, method_name=None, formatter=None):
    if name in attributes:
        return
    if method_name is None:
        method_name = 'get_' + name
    if not hasattr(item, method_name):
        return
    method = getattr(item, method_name)
    value = method()
    attributes[name] = formatter(value) if formatter else value

def get_extended_attributes(item):
    attributes = item.get_attributes()
    maybe_add_attribute(item, attributes, 'label')
    maybe_add_attribute(item, attributes, 'secret_content_type')
    maybe_add_attribute(item, attributes, 'created', formatter=format_date)
    maybe_add_attribute(item, attributes, 'modified', formatter=format_date)
    return attributes

def check_conditions(conditions, attributes):
    for (names, regexp) in conditions:
        value = ''
        for name in names:
            if name in attributes:
                value = attributes[name]
                break
        if not re.search(regexp, value): return False
    return True

def parse_condition_string(arg):
    eq = arg.index('=')
    return re.split(r'[|,]+', arg[:eq]), re.compile(arg[eq+1:])

def all_keyring_items():
    kr = keyring.get_keyring()
    if isinstance(kr, keyring.backends.chainer.ChainerBackend):
        for b in kr.backends:
            if hasattr(b, 'get_preferred_collection'):
                yield from b.get_preferred_collection().get_all_items()
    else:
        yield from kr.get_preferred_collection().get_all_items()

def keyring_items(conditions):
    for item in all_keyring_items():
        attributes = get_extended_attributes(item)
        if check_conditions(conditions, attributes):
            yield item, attributes

def change_passwords(conditions, old_password, new_password, verbosity=1):
    """Change the password in many Gnome Keyring entries to new_password.

Iterate over the keyring keyring_name. Only items matching conditions and where
the current password is old_password are considered. The argument conditions
is a list of elements of the form (names, regexp) where names is a list of
attribute names. An item matches the condition if the value of the first
attribute in names that is present on the item contains a match for regexp.
"""
    for item, attributes in keyring_items(conditions):
        label = attributes['label']
        secret_bytes = item.get_secret()
        if secret_bytes == old_password or \
           secret_bytes == bytes(old_password, 'utf-8'):
            if verbosity >= 1:
                print('Changing:' if new_password is not None else 'Would change:',
                      label)
            if new_password is not None:
                item.set_secret(new_password)
        else:
            if verbosity >= 2:
                print('Has different password, skipping:', label)

def change_password_ui(condition_strings, no_act, verbosity):
    conditions = [parse_condition_string(s) for s in condition_strings]
    old_password = getpass.getpass("Old password: ")
    if no_act:
        new_password = None
    else:
        new_password = getpass2("New password: ")
    change_passwords(conditions, old_password, new_password, verbosity)

def main(args):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--info', action='store_true',
                        help='Print system information and exit')
    parser.add_argument('--no-act', '-n', action='store_true',
                        help='Don\'t actually change passwords, just list entries with a matching password')
    parser.add_argument('--quiet', '-q', action='store_true',
                        help='Print less information')
    parser.add_argument('--verbose', '-v', action='store_true',
                        help='Print more information')
    parser.add_argument('conditions', nargs='*', metavar='CONDITION',
                        help='Only act on entries matching this condition')
    options = parser.parse_args(args)
    if options.info:
        print_info()
        return
    change_password_ui(options.conditions,
                       no_act=options.no_act,
                       verbosity=1 + options.verbose - options.quiet)

if __name__ == '__main__':
    main(sys.argv[1:])

おすすめ記事