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