1*0e16a31dSJim Jagielski#************************************************************** 2*0e16a31dSJim Jagielski# 3*0e16a31dSJim Jagielski# Licensed to the Apache Software Foundation (ASF) under one 4*0e16a31dSJim Jagielski# or more contributor license agreements. See the NOTICE file 5*0e16a31dSJim Jagielski# distributed with this work for additional information 6*0e16a31dSJim Jagielski# regarding copyright ownership. The ASF licenses this file 7*0e16a31dSJim Jagielski# to you under the Apache License, Version 2.0 (the 8*0e16a31dSJim Jagielski# "License"); you may not use this file except in compliance 9*0e16a31dSJim Jagielski# with the License. You may obtain a copy of the License at 10*0e16a31dSJim Jagielski# 11*0e16a31dSJim Jagielski# http://www.apache.org/licenses/LICENSE-2.0 12*0e16a31dSJim Jagielski# 13*0e16a31dSJim Jagielski# Unless required by applicable law or agreed to in writing, 14*0e16a31dSJim Jagielski# software distributed under the License is distributed on an 15*0e16a31dSJim Jagielski# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*0e16a31dSJim Jagielski# KIND, either express or implied. See the License for the 17*0e16a31dSJim Jagielski# specific language governing permissions and limitations 18*0e16a31dSJim Jagielski# under the License. 19*0e16a31dSJim Jagielski# 20*0e16a31dSJim Jagielski 21*0e16a31dSJim Jagielski# usage: runpy.sh fixtures/signed/make.py <port> <outdir> 22*0e16a31dSJim Jagielski# Certificate: fixtures/signed/setup-cert.sh builds runs/task12/gen-signed/nssdb (certutil, one self-signed RSA key). 23*0e16a31dSJim Jagielski# Needs an office started by fixtures/signed/start-ui.sh <strict|ext> <port> (profile runs/profile_t12_sign_<mode>), not 24*0e16a31dSJim Jagielski# start.sh: -headless cancels every modal dialog and the Digital Signatures dialog is the only signing entry point. 25*0e16a31dSJim Jagielski# Saves with the office's configured ODF version, then signs each saved file in place by driving the dialog through 26*0e16a31dSJim Jagielski# its accessibility tree (Sign Document, pick the only certificate, OK, Close). The dialog runs on the office main 27*0e16a31dSJim Jagielski# thread (AsyncCallback), otherwise Execute() has nothing to run on. 28*0e16a31dSJim Jagielskiimport os, sys, time, threading, uno, unohelper 29*0e16a31dSJim Jagielskifrom com.sun.star.awt import XCallback 30*0e16a31dSJim Jagielskifrom com.sun.star.accessibility.AccessibleStateType import ENABLED 31*0e16a31dSJim Jagielskifrom unohelp import FILTERS, connect, desktop, pv 32*0e16a31dSJim Jagielski 33*0e16a31dSJim Jagielskiport, outdir = int(sys.argv[1]), sys.argv[2] 34*0e16a31dSJim Jagielskios.makedirs(outdir, exist_ok=True) 35*0e16a31dSJim Jagielskictx = connect(port) 36*0e16a31dSJim Jagielskism = ctx.ServiceManager 37*0e16a31dSJim Jagielskid = desktop(ctx) 38*0e16a31dSJim Jagielskitk = sm.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx) 39*0e16a31dSJim Jagielskidisp = sm.createInstanceWithContext("com.sun.star.frame.DispatchHelper", ctx) 40*0e16a31dSJim JagielskiPUSH_BUTTON = 44 41*0e16a31dSJim Jagielski 42*0e16a31dSJim Jagielski 43*0e16a31dSJim Jagielskidef build_writer(doc): 44*0e16a31dSJim Jagielski t = doc.Text 45*0e16a31dSJim Jagielski t.insertString(t.createTextCursor(), "Signed text document.", False) 46*0e16a31dSJim Jagielski 47*0e16a31dSJim Jagielski 48*0e16a31dSJim Jagielskidef build_calc(doc): 49*0e16a31dSJim Jagielski sh = doc.Sheets.getByIndex(0) 50*0e16a31dSJim Jagielski sh.getCellByPosition(0, 0).String = "label" 51*0e16a31dSJim Jagielski sh.getCellByPosition(1, 0).Value = 42 52*0e16a31dSJim Jagielski sh.getCellByPosition(1, 1).Formula = "=B1*2" 53*0e16a31dSJim Jagielski 54*0e16a31dSJim Jagielski 55*0e16a31dSJim Jagielskidef build_impress(doc): 56*0e16a31dSJim Jagielski page = doc.DrawPages.getByIndex(0) 57*0e16a31dSJim Jagielski sh = doc.createInstance("com.sun.star.drawing.TextShape") 58*0e16a31dSJim Jagielski page.add(sh) 59*0e16a31dSJim Jagielski sh.String = "Signed presentation" 60*0e16a31dSJim Jagielski 61*0e16a31dSJim Jagielski 62*0e16a31dSJim Jagielskidef build_draw(doc): 63*0e16a31dSJim Jagielski page = doc.DrawPages.getByIndex(0) 64*0e16a31dSJim Jagielski sh = doc.createInstance("com.sun.star.drawing.RectangleShape") 65*0e16a31dSJim Jagielski page.add(sh) 66*0e16a31dSJim Jagielski sh.Size = uno.createUnoStruct("com.sun.star.awt.Size", 4000, 2000) 67*0e16a31dSJim Jagielski sh.String = "Signed drawing" 68*0e16a31dSJim Jagielski 69*0e16a31dSJim Jagielski 70*0e16a31dSJim JagielskiKINDS = (("text", "odt", "swriter", build_writer), ("calc", "ods", "scalc", build_calc), 71*0e16a31dSJim Jagielski ("impress", "odp", "simpress", build_impress), ("draw", "odg", "sdraw", build_draw)) 72*0e16a31dSJim Jagielski 73*0e16a31dSJim Jagielski 74*0e16a31dSJim Jagielskidef find(acc, role, name): 75*0e16a31dSJim Jagielski try: 76*0e16a31dSJim Jagielski c = acc.getAccessibleContext() 77*0e16a31dSJim Jagielski except Exception: 78*0e16a31dSJim Jagielski return None 79*0e16a31dSJim Jagielski if c.getAccessibleRole() == role and c.getAccessibleName() == name: 80*0e16a31dSJim Jagielski return acc 81*0e16a31dSJim Jagielski for i in range(c.getAccessibleChildCount()): 82*0e16a31dSJim Jagielski r = find(c.getAccessibleChild(i), role, name) 83*0e16a31dSJim Jagielski if r: 84*0e16a31dSJim Jagielski return r 85*0e16a31dSJim Jagielski 86*0e16a31dSJim Jagielski 87*0e16a31dSJim Jagielskidef top(title): 88*0e16a31dSJim Jagielski for k in range(tk.getTopWindowCount()): 89*0e16a31dSJim Jagielski w = tk.getTopWindow(k) 90*0e16a31dSJim Jagielski if w is not None and w.getAccessibleContext().getAccessibleName() == title: 91*0e16a31dSJim Jagielski return w 92*0e16a31dSJim Jagielski 93*0e16a31dSJim Jagielski 94*0e16a31dSJim Jagielskidef wait_top(title, secs=30): 95*0e16a31dSJim Jagielski end = time.time() + secs 96*0e16a31dSJim Jagielski while time.time() < end: 97*0e16a31dSJim Jagielski w = top(title) 98*0e16a31dSJim Jagielski if w is not None: 99*0e16a31dSJim Jagielski return w 100*0e16a31dSJim Jagielski time.sleep(0.2) 101*0e16a31dSJim Jagielski raise SystemExit("timeout waiting for window " + title) 102*0e16a31dSJim Jagielski 103*0e16a31dSJim Jagielski 104*0e16a31dSJim Jagielskidef press(win, name): 105*0e16a31dSJim Jagielski b = find(win, PUSH_BUTTON, name) 106*0e16a31dSJim Jagielski if b is None: 107*0e16a31dSJim Jagielski raise SystemExit("no button " + name) 108*0e16a31dSJim Jagielski t = threading.Thread(target=lambda: b.getAccessibleContext().doAccessibleAction(0), daemon=True) 109*0e16a31dSJim Jagielski t.start() 110*0e16a31dSJim Jagielski return t 111*0e16a31dSJim Jagielski 112*0e16a31dSJim Jagielski 113*0e16a31dSJim Jagielskiclass Sign(unohelper.Base, XCallback): 114*0e16a31dSJim Jagielski def __init__(self, frame): 115*0e16a31dSJim Jagielski self.frame, self.done, self.err = frame, False, None 116*0e16a31dSJim Jagielski 117*0e16a31dSJim Jagielski def notify(self, data): 118*0e16a31dSJim Jagielski try: 119*0e16a31dSJim Jagielski disp.executeDispatch(self.frame, ".uno:Signature", "", 0, ()) 120*0e16a31dSJim Jagielski except Exception as e: 121*0e16a31dSJim Jagielski self.err = e 122*0e16a31dSJim Jagielski self.done = True 123*0e16a31dSJim Jagielski 124*0e16a31dSJim Jagielski 125*0e16a31dSJim Jagielskidef sign(path): 126*0e16a31dSJim Jagielski doc = d.loadComponentFromURL(uno.systemPathToFileUrl(path), "_blank", 0, (pv("Hidden", True),)) 127*0e16a31dSJim Jagielski cb = Sign(doc.CurrentController.Frame) 128*0e16a31dSJim Jagielski sm.createInstanceWithContext("com.sun.star.awt.AsyncCallback", ctx).addCallback(cb, None) 129*0e16a31dSJim Jagielski dlg = wait_top("Digital Signatures") 130*0e16a31dSJim Jagielski press(dlg, "Sign Document") 131*0e16a31dSJim Jagielski chooser = wait_top("Select Certificate") 132*0e16a31dSJim Jagielski pick_certificate(chooser) 133*0e16a31dSJim Jagielski press(chooser, "OK") 134*0e16a31dSJim Jagielski end = time.time() + 30 135*0e16a31dSJim Jagielski while top("Select Certificate") is not None and time.time() < end: 136*0e16a31dSJim Jagielski time.sleep(0.2) 137*0e16a31dSJim Jagielski dlg = wait_top("Digital Signatures") 138*0e16a31dSJim Jagielski time.sleep(1) 139*0e16a31dSJim Jagielski press(dlg, "Close") 140*0e16a31dSJim Jagielski end = time.time() + 30 141*0e16a31dSJim Jagielski while not cb.done and time.time() < end: 142*0e16a31dSJim Jagielski time.sleep(0.2) 143*0e16a31dSJim Jagielski if not cb.done or cb.err: 144*0e16a31dSJim Jagielski raise SystemExit("signing did not finish: %r" % cb.err) 145*0e16a31dSJim Jagielski doc.close(True) 146*0e16a31dSJim Jagielski 147*0e16a31dSJim Jagielski 148*0e16a31dSJim Jagielskidef pick_certificate(chooser): 149*0e16a31dSJim Jagielski ok = find(chooser, PUSH_BUTTON, "OK").getAccessibleContext() 150*0e16a31dSJim Jagielski # The chooser preselects the only certificate of the throw-away database. 151*0e16a31dSJim Jagielski if not ok.getAccessibleStateSet().contains(ENABLED): 152*0e16a31dSJim Jagielski raise SystemExit("no certificate preselected in the chooser") 153*0e16a31dSJim Jagielski 154*0e16a31dSJim Jagielski 155*0e16a31dSJim Jagielskifor kind, ext, factory, build in KINDS: 156*0e16a31dSJim Jagielski out = os.path.join(outdir, "signed-%s.%s" % (kind, ext)) 157*0e16a31dSJim Jagielski doc = d.loadComponentFromURL("private:factory/" + factory, "_blank", 0, (pv("Hidden", True),)) 158*0e16a31dSJim Jagielski build(doc) 159*0e16a31dSJim Jagielski doc.storeToURL(uno.systemPathToFileUrl(out), (pv("FilterName", FILTERS[ext]),)) 160*0e16a31dSJim Jagielski doc.close(True) 161*0e16a31dSJim Jagielski sign(out) 162*0e16a31dSJim Jagielski print(out) 163