~mvo/ubuntu-sso-client/strawman-lp711413

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/usr/bin/python

# ubuntu-sso-login - Client side log-in utility for Ubuntu One
#
# Author: Rodney Dawes <rodney.dawes@canonical.com>
#
# Copyright 2009 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.

import gtk
import pango
import sys
import gettext

from gettext import gettext as _
gettext.textdomain('ubuntu-sso-client')

import dbus.service

from dbus.mainloop.glib import DBusGMainLoop

from ubuntu_sso import DBUS_IFACE_AUTH_NAME, DBUS_BUS_NAME, DBUS_CRED_PATH
from ubuntu_sso.main import Login, SSOLogin, SSOCredentials

from ubuntu_sso.logger import setupLogging
logger = setupLogging("ubuntu-sso-login")

gtk.gdk.threads_init()
DBusGMainLoop(set_as_default=True)

OAUTH_REALM = "https://ubuntuone.com"
OAUTH_CONSUMER = "ubuntuone"

NOTIFY_ICON_SIZE = 48

# Why thank you GTK+ for enforcing style-set and breaking API
RCSTYLE = """
style 'dialogs' {
  GtkDialog::action-area-border = 12
  GtkDialog::button-spacing = 6
  GtkDialog::content-area-border = 0
}
widget_class '*Dialog*' style 'dialogs'
"""
U1_PAIR_RECORD = "ubuntu_one_pair_record"
MAP_JS = """function(doc) {
  if (doc.service_name == "ubuntuone") {
    if (doc.application_annotations &&
        doc.application_annotations["Ubuntu One"] &&
        doc.application_annotations["Ubuntu One"]["%s"] &&
        doc.application_annotations["Ubuntu One"]["%s"]["deleted"]) {
        emit(doc._id, 1);
    } else {
        emit(doc._id, 0)
    }
  }
}""" % ("private_application_annotations", "private_application_annotations")


class LoginMain(object):
    """Main login manager process class."""

    def __init__(self, *args, **kw):
        """Initializes the child threads and dbus monitor."""
        gtk.rc_parse_string(RCSTYLE)

        logger.info("Starting Ubuntu SSO login manager")

        self.__bus = dbus.SessionBus()

    def _connect_dbus_signals(self):
        """Set up some signal handlers for DBus signals."""
        self.__bus.add_signal_receiver(
              handler_function=self.new_credentials,
              signal_name="NewCredentials",
              dbus_interface=DBUS_IFACE_AUTH_NAME)
        self.__bus.add_signal_receiver(
              handler_function=self.auth_denied,
              signal_name="AuthorizationDenied",
              dbus_interface=DBUS_IFACE_AUTH_NAME)
        self.__bus.add_signal_receiver(
              handler_function=self.got_oauth_error,
              signal_name="OAuthError",
              dbus_interface=DBUS_IFACE_AUTH_NAME)

    def new_credentials(self, realm=None, consumer_key=None, sender=None):
        """Signal callback for when we get new credentials."""
        self.set_up_desktopcouch_pairing(consumer_key)

        def got_port(*args, **kwargs):
            # Discard the value.  We don't really care about the port, here.
            pass

        def got_error(*args, **kwargs):
            logger.warn("On trying to start desktopcouch-service via DBus, "
                    "we got an error.  %s %s" % (args, kwargs,))

        # We have auth, so start desktopcouch service by asking for its port.
        dc_serv_proxy = self.__bus.get_object("org.desktopcouch.CouchDB", "/")
        dc_serv_proxy.getPort(reply_handler=got_port, error_handler=got_error)

    def auth_denied(self):
        """Signal callback for when auth was denied by user."""
        logger.info("Access to Ubuntu One was denied.")

        from twisted.internet import reactor
        reactor.stop()

    def got_oauth_error(self, message=None):
        """Signal callback for when an OAuth error occured."""
        def dialog_response(dialog, response):
            """Handle the dialog closing."""
            dialog.destroy()

        if message:
            logger.error(message)
            dialog = gtk.Dialog(title="Ubuntu One: Error",
                                flags=gtk.DIALOG_NO_SEPARATOR,
                                buttons=(gtk.STOCK_CLOSE,
                                         gtk.RESPONSE_CLOSE))
            dialog.set_default_response(gtk.RESPONSE_CLOSE)
            dialog.set_icon_name("ubuntu-sso-client")

            area = dialog.get_content_area()

            hbox = gtk.HBox(spacing=12)
            hbox.set_border_width(12)
            area.pack_start(hbox)
            hbox.show()

            image = gtk.Image()
            image.set_from_icon_name("dialog-error", gtk.ICON_SIZE_DIALOG)
            image.set_alignment(0.5, 0.0)
            image.show()
            hbox.pack_start(image, False, False)

            vbox = gtk.VBox(spacing=12)
            vbox.show()
            hbox.pack_start(vbox)

            label = gtk.Label("<b>%s</b>" % _("Authorization Error"))
            label.set_use_markup(True)
            label.set_alignment(0.0, 0.5)
            label.show()
            vbox.pack_start(label, False, False)

            label = gtk.Label(message)
            label.set_line_wrap(True)
            label.set_max_width_chars(64)
            label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
            label.set_alignment(0.0, 0.0)
            label.show()
            vbox.pack_start(label, True, True)

            dialog.connect('close', dialog_response, gtk.RESPONSE_CLOSE)
            dialog.connect('response', dialog_response)

            dialog.show()
        else:
            logger.error("Got an OAuth error with no message.")

    def set_up_desktopcouch_pairing(self, consumer_key):
        """Add a pairing record between desktopcouch and Ubuntu One"""
        try:
            from desktopcouch.pair.couchdb_pairing.couchdb_io import \
                 put_static_paired_service
            from desktopcouch.records.server import CouchDatabase
        except ImportError:
            # desktopcouch is not installed
            logger.debug("Not adding desktopcouch pairing since desktopcouch "
                         "is not installed")
            return
        # Check whether there is already a record of the Ubuntu One service
        db = CouchDatabase("management", create=True)
        if not db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD):
            db.add_view(U1_PAIR_RECORD, MAP_JS, None, U1_PAIR_RECORD)
        results = db.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)
        found = False
        # Results should contain either one row or no rows
        # If there is one row, its value will be 0, meaning that there is
        #   already an Ubuntu One pairing record, or 1, meaning that there
        #   was an Ubuntu One pairing record but it has since been unpaired
        # Only create a new record if there is not one already. Specifically,
        #   do not add the record if there is a deleted one, as this means
        #   that the user explicitly unpaired it!
        for row in results:
            found = True
            if row.value == 1:
                logger.debug("Not adding desktopcouch pairing since the user "
                             "has explicitly unpaired with Ubuntu One")
            else:
                logger.debug("Not adding desktopcouch pairing since we are "
                             "already paired")
        if not found:
            put_static_paired_service(None, "ubuntuone")
            logger.debug("Pairing desktopcouch with Ubuntu One")

    def main(self):
        """Starts the gtk main loop."""
        self._connect_dbus_signals()

        from twisted.internet import reactor
        reactor.run()


if __name__ == "__main__":
    # Register DBus service for making sure we run only one instance
    bus = dbus.SessionBus()
    name = bus.request_name(DBUS_BUS_NAME,
                            dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
    if name == dbus.bus.REQUEST_NAME_REPLY_EXISTS:
        print "Ubuntu One login manager already running, quitting"
        sys.exit(0)

    from twisted.internet import gtk2reactor
    gtk2reactor.install()

    login = Login(dbus.service.BusName(DBUS_BUS_NAME,
                                             bus=dbus.SessionBus()))
    ssoLogin = SSOLogin(dbus.service.BusName(DBUS_BUS_NAME,
                                             bus=dbus.SessionBus()))
    SSOCredentials(dbus.service.BusName(DBUS_BUS_NAME,
                                        bus=dbus.SessionBus()),
                   object_path=DBUS_CRED_PATH)

    manager = LoginMain()
    manager.main()