Source code for plugins.db

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# Copyright (C) 2008-2016 Glencoe Software, Inc. All Rights Reserved.
# Use is subject to license terms supplied in LICENSE.txt
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""
   Plugin for our managing the OMERO database.

   Plugin read by omero.cli.Cli during initialization. The method(s)
   defined here will be added to the Cli class for later use.
"""

from omero.cli import BaseControl
from omero.cli import CLI

from argparse import FileType, SUPPRESS

from omero.install.windows_warning import windows_warning, WINDOWS_WARNING

from omero_ext.path import path

import omero.java
import platform
import sys
import time


HELP = """Database tools for creating scripts, setting passwords, etc."""


if platform.system() == 'Windows':
    HELP += ("\n\n%s" % WINDOWS_WARNING)


[docs] class DatabaseControl(BaseControl): def _configure(self, parser): sub = parser.sub() script = sub.add_parser( "script", help="Generates a DB creation script") script.set_defaults(func=self.script) try: # Python 3 ft = FileType(mode="w", encoding='utf-8') except TypeError: ft = FileType(mode="w") script.add_argument( "-f", "--file", type=ft, help="Optional file to save to. Use '-' for stdout.") script.add_argument("posversion", nargs="?", help=SUPPRESS) script.add_argument("pospatch", nargs="?", help=SUPPRESS) script.add_argument("pospassword", nargs="?", help=SUPPRESS) script.add_argument("--version", help=SUPPRESS) script.add_argument("--patch", help=SUPPRESS) script.add_argument("--password", help="OMERO root password") pw = sub.add_parser( "password", help="Prints SQL command for updating your root password") pw.set_defaults(func=self.password) pw_spec = pw.add_mutually_exclusive_group() pw_spec.add_argument("password", nargs="?") pw_spec.add_argument("--empty", action="store_true", help=("Remove the password, " "allowing any for login when guest.")) pw.add_argument("--user-id", help="User ID to salt into the password. " "Defaults to '0', i.e. 'root'", default="0") for x in (pw, script): x.add_argument( "--no-salt", action="store_true", help="Disable the salting of passwords") def _lookup(self, key, defaults, args): """ Get a value from a flag arg, positional arg, or default properties """ propname = "omero.db." + key vdef = defaults.properties.getProperty(propname) varg = getattr(args, key) vpos = getattr(args, 'pos' + key) if varg: if vpos: self.ctx.die( 1, "ERROR: Flag and positional argument given for %s" % key ) v = varg elif vpos: v = vpos elif vdef: v = vdef else: self.ctx.die(1, "No value found for %s" % propname) self.ctx.err("Using %s for %s" % (v, key)) return v def _has_user_id(self, args): return args and "user_id" in args and args.user_id is not None def _get_password_hash(self, args, root_pass=None, old_prompt=False): prompt = " for OMERO " if self._has_user_id(args) and not old_prompt: prompt += "user %s" % args.user_id else: prompt += "root user" root_pass = self._ask_for_password(prompt, root_pass) jars = str(self.ctx.dir / "lib" / "server") + "/*" cmd = ["ome.security.auth.PasswordUtil", root_pass] if not args.no_salt and self._has_user_id(args): cmd.append(args.user_id) p = omero.java.popen(["-cp", jars] + cmd) rc = p.wait() if rc != 0: out, err = p.communicate() self.ctx.die(rc, "PasswordUtil failed: %s" % err.decode( errors='replace')) value = p.communicate()[0] if not value or len(value) == 0: self.ctx.die(100, "Encoded password is empty") return value.strip().decode() def _copy(self, input_path, output, func, cfg=None): try: input = open(str(input_path), encoding='utf-8') except TypeError: input = open(str(input_path)) try: for s in input: try: if cfg: output.write(func(s) % cfg) else: output.write(func(s)) except Exception as e: self.ctx.dbg(str(e)) self.ctx.die( 154, "Failed to map line: %s\nError: %s" % (s, e)) finally: input.close() def _make_replace(self, root_pass, db_vers, db_patch): def fix(str_in): if isinstance(str_in, bytes): return str_in.decode("utf-8") return str_in def replace_method(str_in): str_out = str_in.replace("@ROOTPASS@", fix(root_pass)) str_out = str_out.replace("@DBVERSION@", fix(db_vers)) str_out = str_out.replace("@DBPATCH@", fix(db_patch)) return str_out return replace_method def _db_profile(self): from omero.install.config_parser import PropertyParser property_lines = self.ctx.get_config_property_lines(self.dir) for property in PropertyParser().parse_lines(property_lines): if property.key == 'omero.db.profile': return property.val raise KeyError('Configuration key not set: omero.db.profile') def _sql_directory(self, db_vers, db_patch): """ See #2689 """ dbprofile = self._db_profile() sql_directory = self.ctx.dir / "sql" / dbprofile / \ ("%s__%s" % (db_vers, db_patch)) if not sql_directory.exists(): self.ctx.die(2, "Invalid Database version/patch: %s does not" " exist" % sql_directory) return sql_directory def _create(self, sql_directory, db_vers, db_patch, password_hash, args, location=None): sql_directory = self._sql_directory(db_vers, db_patch) if not sql_directory.exists(): self.ctx.die(2, "Invalid Database version/patch: %s does not" " exist" % sql_directory) if args and args.file: output = args.file script = "<filename here>" else: script = "%s__%s.sql" % (db_vers, db_patch) location = path.getcwd() / script try: output = open(location, 'w', encoding='utf-8') except TypeError: output = open(location, 'w') self.ctx.out("Saving to " + location) try: dbprofile = self._db_profile() header = sql_directory / ("%s-header.sql" % dbprofile) footer = sql_directory / ("%s-footer.sql" % dbprofile) if header.exists(): # 73 multiple DB support. OMERO 4.3+ cfg = { "TIME": time.ctime(time.time()), "DIR": sql_directory, "SCRIPT": script} self._copy(header, output, str, cfg) self._copy(sql_directory / "schema.sql", output, str) self._copy(sql_directory / "views.sql", output, str) self._copy( footer, output, self._make_replace(password_hash, db_vers, db_patch), cfg) else: # OMERO 4.2.x and before output.write(""" -- -- GENERATED %s from %s -- -- This file was created by the `omero db script` command -- and contains an MD5 version of your OMERO root users's password. -- You should think about deleting it as soon as possible. -- -- To create your database: -- -- createdb omero -- psql omero < %s -- BEGIN; """ % (time.ctime(time.time()), sql_directory, script)) self._copy(sql_directory / "schema.sql", output, str) self._copy( sql_directory / "data.sql", output, self._make_replace(password_hash, db_vers, db_patch)) self._copy(sql_directory / "views.sql", output, str) output.write("COMMIT;\n") finally: output.flush() if output != sys.stdout: output.close() @windows_warning def password(self, args): root_pass = None user_id = 0 old_prompt = True if self._has_user_id(args): user_id = args.user_id if user_id != '0': # For non-root, use new_prompt old_prompt = False try: root_pass = args.password except Exception as e: self.ctx.dbg("While getting arguments:" + str(e)) if args.empty: password_hash = "" else: password_hash = self._get_password_hash(args, root_pass, old_prompt) self.ctx.out("UPDATE password SET hash = '%s' " "WHERE experimenter_id = %s;""" % (password_hash, user_id)) @windows_warning def loaddefaults(self): try: data2 = self.ctx.initData({}) output = self.ctx.readDefaults() self.ctx.parsePropertyFile(data2, output) except Exception as e: self.ctx.dbg(str(e)) data2 = None return data2 @windows_warning def script(self, args): if args.posversion is not None: self.ctx.err("WARNING: Positional arguments are deprecated") if platform.system() == 'Windows': self.ctx.out("\n%s\n" % WINDOWS_WARNING) defaults = self.loaddefaults() db_vers = self._lookup("version", defaults, args) db_patch = self._lookup("patch", defaults, args) root_pass = args.password if root_pass: if args.pospassword: self.ctx.die( 1, "ERROR: Flag and positional argument given for password" ) else: root_pass = args.pospassword if root_pass: self.ctx.err("Using password from commandline") args.user_id = "0" sql = self._sql_directory(db_vers, db_patch) pwhash = self._get_password_hash(args, root_pass, True) self._create(sql, db_vers, db_patch, pwhash, args)
try: register("db", DatabaseControl, HELP) except NameError: if __name__ == "__main__": cli = CLI() cli.register("db", DatabaseControl, HELP) cli.invoke(sys.argv[1:])