在 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:])

答案2

有一个Pythongnome 密钥环 API 的包。您可以遵循某种教程:弯曲 gnome-keyring-with-python其中有一些搜索和设置密码的示例。

secret-tool包中还有一个命令libsecret可以在 gnome-keyring 中获取/设置密码。

相关内容