Den fre 26 feb. 2021 kl 08:31 skrev Daniel Sahlberg < daniel.l.sahlb...@gmail.com>:
> Moving from users@ to dev@ > [...] I was in a bit of a hurry this morning and didn't test properly. The previous version (store-plaintext-passwords2.txt) contained a number of bugs which I hope I've corrected here: - Didn't properly encode keys/values before checking length. Non-ascii characters in the values raised an exception. - Didn't properly add the lenght to K when writing the hash file (added the key name instead) - Reorganized prompting for password and checking for new file and the -u argument to show the error message before prompting for a password [UX improvement] New version attached. Kind regards, Daniel
Index: contrib/client-side/store-plaintext-password.py =================================================================== --- contrib/client-side/store-plaintext-password.py (nonexistent) +++ contrib/client-side/store-plaintext-password.py (working copy) @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +# Script to store password in plaintext in ~/.subversion/auth/svn.simple/ +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ==================================================================== + +import hashlib +import argparse +import os +import getpass +import sys + +def readHashFile(filename): + """Read a hashfile as written by svn_hash_write2() to a list of tuples (key, value)""" + hash = {} + with open(authfileName, 'r', encoding='utf-8') as file: + while True: + # Expecting K [length] or END + line = file.readline() + if not line: + raise Exception('Parse failed, expected K [length] or END, got EOF') + line = line.strip() + if line.strip() == 'END': + if file.readline(): + raise Exception('Parse failed, unexpected data after END') + return hash + elif line[:1] != 'K': + raise Exception('Parse failed, expected K [length]') + + # Read keyname + key = file.readline() + if not line: + raise Exception('Parse failed, expected keyname') + key = key.strip() + if len(key.encode('utf-8')) != int(line[2:]): + raise Exception('Parse failed, invalid key length {} expected {}'.format(len(key.encode('utf-8')), line[2:])) + + # Read V [length] + line = file.readline() + if not line: + raise Exception('Parse failed, expected V [length], got EOF') + line = line.strip() + if line[:1] != 'V': + raise Exception('Parse failed, expected V [length]') + + # Read value + value = file.readline() + if not value: + raise Exception('Parse failed, expected value') + value = value.strip() + if len(value.encode('utf-8')) != int(line[2:]): + raise Exception('Parse failed, invalid value length {} expected {}'.format(len(value.encode('utf-8')), line[2:])) + + # Store + hash[key] = value + +def writeHashFile(filename, hash): + """Write a list of tuples (key, value) to a hashfile of the same format as svn_hash_write2()""" + with open(filename + '.tmp', 'x', encoding='utf-8') as file: + for key, val in hash.items(): + file.write('K {}\n'.format(len(key.encode('utf-8')))) + file.write('{}\n'.format(key)) + file.write('V {}\n'.format(len(val.encode('utf-8')))) + file.write('{}\n'.format(val)) + file.write('END\n') + os.rename(filename + '.tmp', filename) + +# Parse arguments +parser = argparse.ArgumentParser(description='''Store plain-text password in ~/.subversion/auth/svn.simple/\n\n +Existing passwords and authentication realms can be inspected by:\n\n + svn auth [--show-passwords]\n\n +The authentication realm can also be found using:\n\n + svn info URL\n\n +Realm will be read from stdin if not provided on the command line.''', formatter_class=argparse.RawDescriptionHelpFormatter) +parser.add_argument('realm', help='Server authentication real') +parser.add_argument('-u', '--user', help='Set username', nargs='?') +args = parser.parse_args() + +# The file name is the md5encoding of the realm +m = hashlib.md5() +m.update(args.realm.encode('utf-8')) +authfileName = os.path.join(os.path.expanduser('~/.subversion/auth/svn.simple/'), m.hexdigest()) + +# If new file, check that username was given as an argument before prompting for password +if not os.path.isfile(authfileName) and args.user is None: + print('New file, username required (see -u)\n', file=sys.stderr) + parser.print_help() + quit() + +# Prompt for password +password = getpass.getpass("Enter password: ") + +# In an existing file, we add/replace password/username/passtype +if os.path.isfile(authfileName): + hash = readHashFile(authfileName) + if args.user is not None: + hash['username'] = args.user + hash['password'] = password + hash['passtype'] = 'simple' + +# For a new file, set realmstring, username, password and passtype +else: + hash = {} + hash['svn:realmstring'] = args.realm + hash['username'] = args.user + hash['passtype'] = 'simple' + hash['password'] = password + +# Write out the resulting file +writeHashFile(authfileName, hash) Property changes on: contrib/client-side/store-plaintext-password.py ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property