root.py 13 KB
Newer Older
1
2
3
# -*- coding: utf-8 -*-

# imports
Gero Müller's avatar
Gero Müller committed
4
5
6
7
import StringIO
import inspect
import logging
import os
Gero Müller's avatar
Gero Müller committed
8
import uuid
Gero Müller's avatar
Gero Müller committed
9

Gero Müller's avatar
Gero Müller committed
10
from vispa.controller import AbstractController, strongly_expire
11
12
from vispa.controller.ajax import AjaxController
from vispa.controller.bus import BusController
Marcel's avatar
Marcel committed
13
from vispa.controller.error import ErrorController
Gero Müller's avatar
Gero Müller committed
14
15
from vispa.controller.filesystem import FSController
from vispa.models.preference import VispaPreference, ExtensionPreference
16
from vispa.models.user import User
17
from vispa.models.group import Group, Group_User_Assoc
fabian's avatar
fabian committed
18
from vispa.models.project import Project
19
from vispa.models.workspace import Workspace
20
import cherrypy
Gero Müller's avatar
Gero Müller committed
21
import vispa
22

Gero Müller's avatar
Gero Müller committed
23
24
from vispa.models.shortcuts import VispaShortcuts, ExtensionShortcuts
import json as JSON
25
from vispa import socketbus
Gero Müller's avatar
Gero Müller committed
26
logger = logging.getLogger(__name__)
27

28

29
class RootController(AbstractController):
30
31

    def __init__(self, server):
32
        """
33
34
        The Constructor. Members from other classes
        are added as pages for cherrypy URL mapping.
35
        """
Gero Müller's avatar
Gero Müller committed
36
        AbstractController.__init__(self, mount_static=False)
37
38
39

        self._server = server

Gero Müller's avatar
Gero Müller committed
40
41
42
43
44
45
46
47
48
        static_path = os.path.join(os.path.dirname(inspect.getabsfile(vispa)), 'static')
        build_path = vispa.config("web", "build_path", None)
        if build_path:
            build_path = os.path.join(os.path.dirname(inspect.getabsfile(vispa)), build_path, 'static')
            if not os.path.isdir(build_path):
                logger.warning("RootController: build_path '%s' not found, falling back to default.", build_path)
            else:
                static_path = build_path
        self.mount_static(static_path)
49
        self.ajax = AjaxController(self)
50
        self.fs = FSController()
Gero Müller's avatar
Gero Müller committed
51
        self.extensions = AbstractController(mount_static=False)
52
        self.bus = BusController()
53

54
        if not vispa.config("web", "dev_mode", True):
55
56
            self.error = ErrorController()

Gero Müller's avatar
Gero Müller committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
        self.workspace_action = vispa.config("web", "workspace_action", "")
        self.use_websockets   = vispa.config("websockets", "enabled", False)
        self.client_log_level = vispa.config("web", "logging.level", "info")
        self.add_workspaces   = vispa.config("workspace", "add", True)
        self.alter_workspaces = vispa.config("workspace", "alter", True)
        self.use_feedback     = vispa.config("web", "feedback.address", "") != ""
        self.cache_bust       = vispa.config("web", "cache_bust", "")
        if self.cache_bust == "uuid":
             self.cache_bust = str(uuid.uuid4())
        elif self.cache_bust.startswith("mtime:"):
             self.cache_bust = str(os.path.getmtime(self.cache_bust[6:]))
        elif len(self.cache_bust) == 0:
             self.cache_bust = None

71
72
    def mount_extension_controller(self, mountpoint, controller):
        if hasattr(self.extensions, mountpoint):
73
            logger.warning("Controller mountpoint already exists: %s" % mountpoint)
74
        else:
75
            logger.info("Mounting controller '%s'" % os.path.join(os.sep, mountpoint))
76
            setattr(self.extensions, mountpoint, controller)
77

78
79
80
81
    def get_preferences(self, db, user_id, parse_json=False):
        preferences = {}
        preferences["vispa_preferences"] = VispaPreference.get_data_by_user_id(db, user_id)
        preferences["extension_preferences"] = ExtensionPreference.get_data_by_user_id(db, user_id)
82
83
        preferences["vispa_shortcuts"] = VispaShortcuts.get_data_by_user_id(db, user_id)
        preferences["extension_shortcuts"] = ExtensionShortcuts.get_data_by_user_id(db, user_id)
84
85
86
87
88
        # parse?
        if not parse_json:
            return preferences
        else:
            parsed_preferences = {}
89
90
91
92
93
            try:
                for key, value in preferences.items():
                    parsed_preference = {}
                    for key2, value2 in value.items():
                        parsed_preference[key2] = JSON.loads(value2)
Benjamin Fischer's avatar
Benjamin Fischer committed
94
                    parsed_preferences[key] = JSON.dumps(parsed_preference)
95
96
            except:
                logger.exception("parse prefernces")
97
98
            return parsed_preferences

99
    def workspace_data(self, workspace=None, keys=None):
100
101
102
        db   = cherrypy.request.db
        user = cherrypy.request.user

103
        workspace_data = {}
104
105
106
107
108
109
110
111
112
113

        if workspace:
            workspaces = [workspace]
        else:
            workspaces = Workspace.get_user_workspaces(db, user)

        for ws in workspaces:
            data = ws.make_dict(keys=keys)

            conn = vispa.workspace._connection_pool.get(user, ws)
114
            data["connection_status"] = conn.status()[1] if conn else "disconnected"
115
116

            if workspace == ws:
117
                return data
118
119
120

            workspace_data[ws.id] = data

121
122
        return workspace_data

Marcel's avatar
Marcel committed
123
    @cherrypy.expose
124
    @cherrypy.tools.render(template="sites/index.mako")
125
    @cherrypy.tools.method(accept="GET")
Gero Müller's avatar
Gero Müller committed
126
    @strongly_expire
127
    def index(self, *args, **kwargs):
Gero Müller's avatar
Gero Müller committed
128
        db = cherrypy.request.db
129
        user = cherrypy.request.user
Gero Müller's avatar
Gero Müller committed
130
        preferences = self.get_preferences(db, cherrypy.request.user.id,
131
132
                                                parse_json=True)

133
        try:
134
135
            if vispa.config("usermanagement", "autosetup", False):
                global_project = vispa.config("usermanagement", "global_project", None)
fabian's avatar
fabian committed
136
                global_project = Project.get_or_create_by_name(db, global_project)
137
138
139
                globalPermissions = user.get_permissions(global_project)
                extensions = []
                for extension in self._server._extensions.keys():
140
141
142
143
                    extensionAllowed = vispa.models.role.Permission\
                                       .get_by_name(db, unicode(extension + ".allowed"))
                    if extensionAllowed:
                        if extensionAllowed in globalPermissions:
144
145
                            extensions.append(extension)
                    else:
146
                        extensions.append(extension)
147
148
            else:
                extensions = self._server._extensions.keys()
149
150
151
152
        except:
            extensions = self._server._extensions.keys()


Marcel Rieger's avatar
Marcel Rieger committed
153
        data = {
154
155
            "username"        : user.name,
            "usermail"        : user.email,
Gero Müller's avatar
Gero Müller committed
156
157
158
159
160
            "use_websockets"  : self.use_websockets,
            "add_workspaces"  : self.add_workspaces,
            "alter_workspaces": self.alter_workspaces,
            "log_level"       : self.client_log_level,
            "use_feedback"    : self.use_feedback,
161
            "is_guest"        : cherrypy.session.get("is_guest", False),
162
            "is_admin"        : user.serveradmin,
Gero Müller's avatar
Gero Müller committed
163
            "workspace_action": self.workspace_action,
Benjamin Fischer's avatar
Benjamin Fischer committed
164
            "extensions"      : JSON.dumps(extensions),
Gero Müller's avatar
Gero Müller committed
165
            "cache_bust"      : self.cache_bust,
166
        }
167
        data.update(preferences)
168

Marcel Rieger's avatar
Marcel Rieger committed
169
        return data
170
171

    @cherrypy.expose
172
    @cherrypy.tools.user(reverse=True)
173
    @cherrypy.tools.render(template="sites/login.mako")
174
    @cherrypy.tools.method(accept="GET")
Marcel's avatar
Marcel committed
175
    def login(self, *args, **kwargs):
176
        path = vispa.url.dynamic("?" + cherrypy.request.query_string)
177
        db = cherrypy.request.db
178
179
180
181

        if "user_id" in cherrypy.session:
            raise cherrypy.HTTPRedirect(path)

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
        login = cherrypy.request.login
        if login and vispa.config("user", "remote.enabled", False):
            user = User.get_by_name(db, login)

            if user is None and vispa.config("user", "remote.auto_create", True):
                email_key = vispa.config("user", "remote.email_key", None)
                email_domain = vispa.config("user", "remote.email_domain", None)
                if email_key:
                    email = cherrypy.request.wsgi_environ.get(email_key, None)
                elif email_domain:
                    email = "%s@%s" % (login, email_domain)
                user = User(name=login, password=User.generate_hash(64), email=email, hash=User.generate_hash(32), status=User.ACTIVE)
                db.add(user)
                db.commit()
                vispa.fire_callback("user.register", user)
                vispa.fire_callback("user.activate", user)
198

199
200
201
202
203
204
            cherrypy.session["user_id"] = unicode(user.id)
            cherrypy.session["user_name"] = user.name
            vispa.fire_callback("user.login", user)
            raise cherrypy.HTTPRedirect(path)


205
206
207
208
        welcome_phrase = vispa.config("web", "text.welcome", "")
        login_text = vispa.config("web", "text.login", "")
        registration_text = vispa.config("web", "text.registration", "")
        forgot_text = vispa.config("web", "text.forgot", "")
209
        disclaimer_text = vispa.config("web", "text.disclaimer", "")
210
        use_forgot = vispa.config("web", "forgot.use", False)
211
212
213
214
215
        return {
            "welcome_phrase"   : welcome_phrase,
            "login_text"       : login_text,
            "registration_text": registration_text,
            "forgot_text"      : forgot_text,
216
            "disclaimer_text"  : disclaimer_text,
Gero Müller's avatar
Gero Müller committed
217
            "use_forgot"       : use_forgot,
218
            "cache_bust"       : self.cache_bust,
219
        }
220

Gero Müller's avatar
Gero Müller committed
221
    @cherrypy.expose
Marcel Rieger's avatar
Marcel Rieger committed
222
    @cherrypy.tools.user(on=False)
223
    @cherrypy.tools.method(accept="GET")
224
    def guest_login(self, *args, **kwargs):
225
        path = vispa.url.dynamic("?" + cherrypy.request.query_string)
226
227

        # redirect when the user is already logged in
228
        if "user_id" in cherrypy.session:
229
230
            raise cherrypy.HTTPRedirect(path)

231
        # return an error when guest logins are disabled
Gero Müller's avatar
Gero Müller committed
232
        if vispa.config("usermanagement", "enable_guest_login", False) is False:
233
234
            raise cherrypy.HTTPError(403, "Guest login not allowed!")

Gero Müller's avatar
Gero Müller committed
235
236
        db= cherrypy.request.db
        
237
        # actual guest login
Gero Müller's avatar
Gero Müller committed
238
239
        user, password = User.guest_login(db)
        for groupname in vispa.config("usermanagement", "guest_group", "guest"):
Gero Müller's avatar
Gero Müller committed
240
            group = Group.get_or_create_by_name(db, groupname)
Gero Müller's avatar
Gero Müller committed
241
            group.add_user(db, user, Group_User_Assoc.UNCONFIRMED)
242

243
        # update session
Gero Müller's avatar
Gero Müller committed
244
245
        cherrypy.session["user_id"] = unicode(user.id)
        cherrypy.session["user_name"] = user.name
Gero Müller's avatar
Gero Müller committed
246
        cherrypy.session["guest_password"] = password
Gero Müller's avatar
Gero Müller committed
247
        vispa.fire_callback("user.login", user)
248
        cherrypy.session["is_guest"] = True
Gero Müller's avatar
Gero Müller committed
249

250
        raise cherrypy.HTTPRedirect(path)
Gero Müller's avatar
Gero Müller committed
251

252
    @cherrypy.expose
253
    @cherrypy.tools.method(accept="GET")
254
    def logout(self, path="/"):
Gero Müller's avatar
Gero Müller committed
255
        vispa.fire_callback("user.logout", cherrypy.request.user)
256
        vispa.socketbus.remove_session(self.get("window_id"))
257
        cherrypy.lib.sessions.expire()
marcel's avatar
marcel committed
258
259
260
261
262
263
264

        # remove all cookies
        for key in cherrypy.response.cookie.keys():
            cherrypy.response.cookie[key] = ""
            cherrypy.response.cookie[key]["expires"] = 0
            cherrypy.response.cookie[key]["max-age"] = 0

265
        raise cherrypy.HTTPRedirect(vispa.url.dynamic(path))
266
267

    @cherrypy.expose
268
    @cherrypy.tools.user(on=False)
269
    @cherrypy.tools.render(template="sites/password.mako")
270
    @cherrypy.tools.method(accept="GET")
271
272
273
    def password(self, hash, *args, **kwargs):
        user = User.get_by_hash(cherrypy.request.db, hash)
        data = {
ThorbenQuast's avatar
ThorbenQuast committed
274
            "welcome_phrase": vispa.config("web", "text.welcome", ""),
275
            "username": None if not isinstance(user, User) else user.name,
Gero Müller's avatar
Gero Müller committed
276
            "hash": hash,
marcel's avatar
marcel committed
277
            "cache_bust": self.cache_bust
278
279
        }
        return data
Gero Müller's avatar
Gero Müller committed
280
281
282
283
284
285

    @cherrypy.expose
    @cherrypy.tools.user(on=False)
    @cherrypy.tools.db(on=False)
    @cherrypy.tools.workspace(on=False)
    @cherrypy.tools.sessions(on=False)
Gero Müller's avatar
Gero Müller committed
286
    @cherrypy.tools.method(accept=["GET", "POST"])
Gero Müller's avatar
Gero Müller committed
287
    @strongly_expire
Gero Müller's avatar
Gero Müller committed
288
289
290
291
292
    def status(self, password=None):
        if password != vispa.config("web", "status_password", None):
            return """
<html>
<head>
293
    <title>VISPA Threads</title>
Gero Müller's avatar
Gero Müller committed
294
295
</head>
<body>
296
<h1>VISPA Threads</h1>
297
<form method="post">
Gero Müller's avatar
Gero Müller committed
298
299
300
301
302
303
304
<label for="password">Password:</label>
<input type="password" name="password" />
<input type="submit" value="Submit" />
</form>
</html>
"""
        else:
305
306
307
            s = StringIO.StringIO()
            vispa.dump_thread_status(s)
            return s.getvalue()
308
309
310
311
312
313

    @cherrypy.expose
    @cherrypy.tools.user(on=False)
    @cherrypy.tools.db(on=False)
    @cherrypy.tools.workspace(on=False)
    @cherrypy.tools.sessions(on=False)
Gero Müller's avatar
Gero Müller committed
314
    @cherrypy.tools.method(accept=["GET", "POST"])
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    @strongly_expire
    def graceful_shutdown(self, password=None):
        if password != vispa.config("web", "status_password", None):
            return """
<html>
<head>
    <title>VISPA Graceful Shutdown</title>
</head>
<body>
<h1>VISPA Graceful Shutdown</h1>
<form method="post">
<label for="password">Password:</label>
<input type="password" name="password" />
<input type="submit" value="Submit" />
</form>
</html>
"""
        else:
            logger.warn(
                "Graceful shutdown started")
Gero Müller's avatar
Gero Müller committed
335
            _cb_graceful_shutdown(None, None)
336
337
338
339
340
341
342
343
344
345
346
            vispa.subscribe("bus.session_removed", _cb_graceful_shutdown)


def _cb_graceful_shutdown(window_id, user_id):
        remaining_session = len(socketbus.USER_SESSIONS)
        if remaining_session == 0:
            cherrypy.engine.exit()
        else:
            logger.warn(
                "Graceful shutdown: waiting for %d remaining session." %
                remaining_session)