xref: /trunk/test/odf-validate/fixtures/signed/make.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
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