Author: dsahlberg
Date: Fri Jul  8 23:39:13 2022
New Revision: 1902590

URL: http://svn.apache.org/viewvc?rev=1902590&view=rev
Log:
A new script to store/update a password in the plain text password store

* tools/client-side/store-plaintext-password.py
  As above

Discussed on dev@: 
https://lists.apache.org/thread/jfd0f5n2qpgnyc30dst6ycnkphcwf6mm

Added:
    subversion/trunk/tools/client-side/store-plaintext-password.py   (with 
props)

Added: subversion/trunk/tools/client-side/store-plaintext-password.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/tools/client-side/store-plaintext-password.py?rev=1902590&view=auto
==============================================================================
--- subversion/trunk/tools/client-side/store-plaintext-password.py (added)
+++ subversion/trunk/tools/client-side/store-plaintext-password.py Fri Jul  8 
23:39:13 2022
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+"""\
+Script to store password in plaintext in ~/.subversion/auth/svn.simple/
+
+Useful in case Subversion is compiled without support for writing
+passwords in plaintext.
+
+Only use this script if the security implications are understood
+and it is acceptable by your organization to store passwords in plaintext.
+
+See http://subversion-staging.apache.org/faq.html#plaintext-passwords
+"""
+
+# ====================================================================
+#    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 os
+import sys
+
+TERMINATOR = b"END\n"
+
+PARSERDESCR = """\
+Store plaintext password in ~/.subversion/auth/svn.simple/
+
+Existing passwords and authentication realms can be inspected by:
+
+    svn auth [--show-passwords]
+
+The authentication realm can also be found using:
+
+    svn info URL
+"""
+
+def _read_one_datum(fd, letter):
+    """\
+    Read a 'K <length>\\n<key>\\n' or 'V <length>\\n<value>\\n' block from
+    an svn_hash_write2()-format FD.
+
+    LETTER identifies the first letter, as a bytes object.
+    """
+    assert letter in {b'K', b'V'}
+
+    # Read the letter and the space
+    readletter = fd.read(1)
+    if readletter != letter or fd.read(1) != b' ':
+        raise ValueError('Hash file format error: Expected {} got 
{}'.format(letter, readletter))
+
+    # Read the length and the newline
+    line = fd.readline()
+    if line[-1:] != b'\n':
+        raise ValueError('Hash file format error: Expected trailing \\n')
+    expected_length = int(line[:-1])
+
+    # Read the datum and its newline
+    datum = fd.read(expected_length)
+    if len(datum) != expected_length:
+        raise ValueError('Hash file format error: Expected length {} got 
{}'.format(expected_length, len(datum)))
+    if fd.read(1) != b'\n':
+        raise ValueError('Hash file format error: Extra data after reading {} 
bytes, expected \\n')
+
+    return datum
+
+def svn_hash_read(fd):
+    """\
+    Read an svn_hash_write2()-formatted file from FD, terminated by "END".
+
+    Return a dict mapping bytes to bytes.
+    """
+    assert 'b' in fd.mode
+    assert TERMINATOR[0] not in {b'K', b'V'}
+
+    ret = {}
+    while True:
+        if fd.peek(1)[0] == TERMINATOR[0]:
+            if fd.readline() != TERMINATOR:
+                raise ValueError('Hash file format error: Expected file 
terminator {}'.format(TERMINATOR))
+            if fd.peek(1):
+                raise ValueError('Hash file format error: Extra content after 
file terminator')
+            return ret
+
+        key = _read_one_datum(fd, b'K')
+        value = _read_one_datum(fd, b'V')
+        ret[key] = value
+
+def outputHash(fd, hash):
+    """\
+    Write a dictionary HASH to an open file descriptor FD in the
+    svn_hash_write2()-format, terminated by "END\\n".
+
+    The keys and values must have datatype 'bytes' and strings must be
+    encoded using utf-8.
+    """
+    assert 'b' in fd.mode
+
+    for key, val in dict.items():
+        fd.write(b'K ' + bytes(str(len(key)), 'utf-8') + b'\n')
+        fd.write(key + b'\n')
+        fd.write(b'V ' + bytes(str(len(val)), 'utf-8') + b'\n')
+        fd.write(val + b'\n')
+    fd.write(TERMINATOR)
+
+def writeHashFile(filename, hash):
+    """\
+    Write the dict HASH to a file named FILENAME in svn_hash_write2()
+    format.
+    """
+    tmpFilename = filename + '.tmp'
+    try:
+        with open(tmpFilename, 'xb') as fd:
+            outputHash(fd, dict)
+            os.rename(tmpFilename, filename)
+    except FileExistsError:
+        print('{}: File {!r} already exist. Is the script already running?'
+              .format(os.path.basename(__file__), tmpFilename),
+              file=sys.stderr)
+    except:
+        os.remove(tmpFilename)
+        raise
+
+def main():
+    # These imports are only being used by main
+    import argparse
+    import getpass
+    import hashlib
+
+    # Parse arguments
+    parser = argparse.ArgumentParser(
+        description=PARSERDESCR,
+        formatter_class=argparse.RawDescriptionHelpFormatter)
+    parser.add_argument('realm', help='Server authentication real')
+    parser.add_argument('-u', '--user', help='Set username')
+    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 the authfile doesn't already exist, verify that a username has been 
provided
+    # or else prompt for it
+    existingFile = os.path.exists(authfileName)
+    if not existingFile and args.user is None:
+        args.user = input("Enter username for realm {}: ".format(args.realm))
+        if args.user == '':
+            parser.exit(1, 'Username required.\n')
+
+    # Prompt for password
+    password = getpass.getpass("Enter password for realm {}: 
".format(args.realm))
+
+    # In an existing file, we add/replace password/username/passtype
+    if existingFile:
+        hash = svn_hash_read(open(authfileName, 'rb'))
+        if args.user is not None:
+            hash[b'username'] = args.user.encode('utf-8')
+        hash[b'password'] = password.encode('utf-8')
+        hash[b'passtype'] = b'simple'
+
+    # For a new file, set realmstring, username, password and passtype
+    else:
+        hash = {
+            b'svn:realmstring': args.realm.encode('utf-8'),
+            b'username': args.user.encode('utf-8'),
+            b'passtype': b'simple',
+            b'password': password.encode('utf-8'),
+        }
+
+    del password
+
+    # Write out the resulting file
+    writeHashFile(authfileName, hash)
+
+
+if __name__ == '__main__':
+    main()
+

Propchange: subversion/trunk/tools/client-side/store-plaintext-password.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: subversion/trunk/tools/client-side/store-plaintext-password.py
------------------------------------------------------------------------------
    svn:executable = *


Reply via email to