1
|
|
|
import sys |
2
|
|
|
import os |
3
|
|
|
from configparser import SafeConfigParser |
4
|
|
|
|
5
|
|
|
from django.core.exceptions import ImproperlyConfigured |
6
|
|
|
|
7
|
|
|
script_dir = os.path.dirname(__file__) |
8
|
|
|
VERSION = open(os.path.join(script_dir, 'VERSION')).read() |
9
|
|
|
|
10
|
|
|
NOT_CONFIGURED_VALUE = '***not configured***' |
11
|
|
|
|
12
|
|
|
# Some helper functions |
13
|
|
|
|
14
|
|
|
def find_config_info(): |
15
|
|
|
''' |
16
|
|
|
Determine which configuration file to load. |
17
|
|
|
Returns path to config file and boolean flag for production system status. |
18
|
|
|
|
19
|
|
|
Precedence rules are as follows: |
20
|
|
|
- Developer configuration overrides production configuration on developer machine. |
21
|
|
|
- Linux production system are more likely to happen than Windows developer machines. |
22
|
|
|
|
23
|
|
|
Throws exception if no config file is found. This terminates the application loading. |
24
|
|
|
''' |
25
|
|
|
config_info = ( |
26
|
|
|
('/etc/opensubmit/settings.ini', True), # Linux production system |
27
|
|
|
(os.path.dirname(__file__)+'/settings_dev.ini', False), # Linux / Mac development system |
28
|
|
|
(os.path.expandvars('$APPDATA')+'opensubmit/settings.ini', False), # Windows development system |
29
|
|
|
) |
30
|
|
|
|
31
|
|
|
for config_file, production in config_info: |
32
|
|
|
if os.path.isfile(config_file): |
33
|
|
|
return (config_file, production, ) |
34
|
|
|
|
35
|
|
|
raise IOError("No configuration file found.") |
36
|
|
|
|
37
|
|
|
def ensure_configured(text): |
38
|
|
|
''' |
39
|
|
|
Ensure that the configuration variable value does not have the default setting. |
40
|
|
|
''' |
41
|
|
|
if text == NOT_CONFIGURED_VALUE: |
42
|
|
|
raise ImproperlyConfigured("It is not configured.") |
43
|
|
|
return text |
44
|
|
|
|
45
|
|
|
def ensure_slash(leading, trailing, text): |
46
|
|
|
''' |
47
|
|
|
Slashes are the main source of joy in Django path and URL setups. |
48
|
|
|
Using this method in the rest of the script should make problems and expectations |
49
|
|
|
way more explicit. |
50
|
|
|
|
51
|
|
|
The 'leading' parameter defines if a leading slash is expected. |
52
|
|
|
The 'trailing' parameter defines if a trailing slash is expected. |
53
|
|
|
|
54
|
|
|
It is too early for logging here, so we use the appropriate Django exception. |
55
|
|
|
''' |
56
|
|
|
text = ensure_configured(text) |
57
|
|
|
if len(text)==0: |
58
|
|
|
if leading: |
59
|
|
|
raise ImproperlyConfigured("'%s' should have a leading slash, but it is empty."%text) |
60
|
|
|
if trailing: |
61
|
|
|
raise ImproperlyConfigured("'%s' should have a trailing slash, but it is empty."%text) |
62
|
|
|
return text |
63
|
|
|
if not text[0]=='/' and leading: |
64
|
|
|
raise ImproperlyConfigured("'%s' should have a leading slash."%text) |
65
|
|
|
if not text[-1]=='/' and trailing: |
66
|
|
|
raise ImproperlyConfigured("'%s' should have a trailing slash."%text) |
67
|
|
|
if text[0]=='/' and not leading: |
68
|
|
|
raise ImproperlyConfigured("'%s' shouldn't have a leading slash."%text) |
69
|
|
|
if text[-1]=='/' and not trailing: |
70
|
|
|
raise ImproperlyConfigured("'%s' shouldn't have a trailing slash."%text) |
71
|
|
|
return text |
72
|
|
|
|
73
|
|
|
def ensure_slash_from_config(config, leading, trailing, configvar): |
74
|
|
|
''' |
75
|
|
|
Read configuration file variable and make sure that leading and trailing slashes are correct. |
76
|
|
|
This indirection allows to add the config variable name to the exception details. |
77
|
|
|
''' |
78
|
|
|
try: |
79
|
|
|
return ensure_slash(leading, trailing, config.get(*configvar)) |
80
|
|
|
except ImproperlyConfigured as e: |
81
|
|
|
# The message attribute is deprecated since Python 2.7, so this is the better way to change the text |
82
|
|
|
raise ImproperlyConfigured("The value of configuration variable %s did not pass the sanity check. %s"%(str(configvar),e.message)) |
83
|
|
|
|
84
|
|
|
# Find configuration file and open it. |
85
|
|
|
try: |
86
|
|
|
config_file_path, is_production = find_config_info() |
87
|
|
|
except: |
88
|
|
|
print("Configuration file not found. Please run 'opensubmit-web configure' first.") |
89
|
|
|
exit(0) |
90
|
|
|
|
91
|
|
|
print("Using "+config_file_path) |
92
|
|
|
config = SafeConfigParser() |
93
|
|
|
config.read([config_file_path], encoding='utf-8') |
94
|
|
|
|
95
|
|
|
# Global settings |
96
|
|
|
DATABASES = { |
97
|
|
|
'default': { |
98
|
|
|
'ENGINE': 'django.db.backends.' + config.get('database', 'DATABASE_ENGINE'), |
99
|
|
|
'NAME': ensure_configured(config.get('database', 'DATABASE_NAME')), |
100
|
|
|
'USER': config.get('database', 'DATABASE_USER'), |
101
|
|
|
'PASSWORD': config.get('database', 'DATABASE_PASSWORD'), |
102
|
|
|
'HOST': config.get('database', 'DATABASE_HOST'), |
103
|
|
|
'PORT': config.get('database', 'DATABASE_PORT'), |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
# We have the is_production indicator from above, which could also determine this value. |
108
|
|
|
# But sometimes, you need Django stack traces in your production system for debugging. |
109
|
|
|
DEBUG = config.getboolean('general', 'DEBUG') |
110
|
|
|
|
111
|
|
|
# Determine MAIN_URL / FORCE_SCRIPT option |
112
|
|
|
HOST = ensure_slash_from_config(config, False, False, ('server', 'HOST')) |
113
|
|
|
HOST_DIR = ensure_slash_from_config(config, False, False, ('server', 'HOST_DIR')) |
114
|
|
|
if len(HOST_DIR) > 0: |
115
|
|
|
MAIN_URL = HOST + '/' + HOST_DIR |
116
|
|
|
FORCE_SCRIPT_NAME = '/' + HOST_DIR |
117
|
|
|
else: |
118
|
|
|
MAIN_URL = HOST |
119
|
|
|
FORCE_SCRIPT_NAME = ensure_slash(False, False, '') |
120
|
|
|
|
121
|
|
|
# Determine some settings based on the MAIN_URL |
122
|
|
|
LOGIN_URL = MAIN_URL |
123
|
|
|
LOGIN_ERROR_URL = MAIN_URL |
124
|
|
|
LOGIN_REDIRECT_URL = ensure_slash(False, True, MAIN_URL+'/dashboard/') |
125
|
|
|
|
126
|
|
|
# Local file system storage for uploads. |
127
|
|
|
# Please note that MEDIA_URL is intentionally not set, since all media |
128
|
|
|
# downloads have to use our download API URL for checking permissions. |
129
|
|
|
MEDIA_ROOT = ensure_slash_from_config(config, True, True, ('server', 'MEDIA_ROOT')) |
130
|
|
|
|
131
|
|
|
# Root of the installation |
132
|
|
|
# This is normally detected automatically, so the settings.ini template does |
133
|
|
|
# not contain the value. For the test suite, however, we need the override option. |
134
|
|
|
if config.has_option('general', 'SCRIPT_ROOT'): |
135
|
|
|
SCRIPT_ROOT = ensure_slash(True, False,config.get('general', 'SCRIPT_ROOT')) |
136
|
|
|
else: |
137
|
|
|
SCRIPT_ROOT = ensure_slash(True, False, os.path.dirname(os.path.abspath(__file__))) |
138
|
|
|
|
139
|
|
|
if is_production: |
140
|
|
|
# Root folder for static files |
141
|
|
|
STATIC_ROOT = ensure_slash(True, True, SCRIPT_ROOT + '/static-production/') |
142
|
|
|
STATICFILES_DIRS = (SCRIPT_ROOT + '/static/', ) |
143
|
|
|
# Absolute URL for static files, directly served by Apache on production systems |
144
|
|
|
STATIC_URL = ensure_slash(False, True, MAIN_URL + '/static/') |
145
|
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' |
146
|
|
|
ALLOWED_HOSTS = [MAIN_URL.split('/')[2]] |
147
|
|
|
if ':' in ALLOWED_HOSTS[0]: |
148
|
|
|
ALLOWED_HOSTS = [ALLOWED_HOSTS[0].split(':')[0]] |
149
|
|
|
SERVER_EMAIL = config.get('admin', 'ADMIN_EMAIL') |
150
|
|
|
else: |
151
|
|
|
# Root folder for static files |
152
|
|
|
STATIC_ROOT = ensure_slash(True, True, SCRIPT_ROOT+'/static/') |
153
|
|
|
# Relative URL for static files |
154
|
|
|
STATIC_URL = ensure_slash(True, True,'/static/') |
155
|
|
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' |
156
|
|
|
ALLOWED_HOSTS = ['localhost','127.0.0.1'] |
157
|
|
|
|
158
|
|
|
ADMINS = ( |
159
|
|
|
(config.get('admin', 'ADMIN_NAME'), config.get('admin', 'ADMIN_EMAIL'), ), |
160
|
|
|
) |
161
|
|
|
MANAGERS = ADMINS |
162
|
|
|
EMAIL_SUBJECT_PREFIX = '[OpenSubmit] ' |
163
|
|
|
TIME_ZONE = config.get("server", "TIME_ZONE") |
164
|
|
|
LANGUAGE_CODE = 'en-en' |
165
|
|
|
USE_I18N = True |
166
|
|
|
USE_L10N = True |
167
|
|
|
USE_TZ = False |
168
|
|
|
STATICFILES_FINDERS = ( |
169
|
|
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder', |
170
|
|
|
'django.contrib.staticfiles.finders.FileSystemFinder', |
171
|
|
|
) |
172
|
|
|
SECRET_KEY = config.get("server", "SECRET_KEY") |
173
|
|
|
|
174
|
|
|
TEMPLATES = [ |
175
|
|
|
{ |
176
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
177
|
|
|
'OPTIONS': {'debug': DEBUG, |
178
|
|
|
'context_processors': |
179
|
|
|
("django.contrib.auth.context_processors.auth", |
180
|
|
|
"django.template.context_processors.debug", |
181
|
|
|
"django.template.context_processors.i18n", |
182
|
|
|
"django.template.context_processors.media", |
183
|
|
|
"django.template.context_processors.static", |
184
|
|
|
"django.template.context_processors.tz", |
185
|
|
|
"django.contrib.messages.context_processors.messages", |
186
|
|
|
"opensubmit.contextprocessors.footer", |
187
|
|
|
"django.template.context_processors.request", |
188
|
|
|
"social_django.context_processors.backends", |
189
|
|
|
"social_django.context_processors.login_redirect" |
190
|
|
|
) |
191
|
|
|
}, |
192
|
|
|
'APP_DIRS': True, |
193
|
|
|
}, |
194
|
|
|
] |
195
|
|
|
|
196
|
|
|
TEST_RUNNER = 'opensubmit.tests.DiscoverRunner' |
197
|
|
|
|
198
|
|
|
MIDDLEWARE_CLASSES = ( |
199
|
|
|
'django.middleware.common.CommonMiddleware', |
200
|
|
|
'django.contrib.sessions.middleware.SessionMiddleware', |
201
|
|
|
'django.middleware.csrf.CsrfViewMiddleware', |
202
|
|
|
'django.contrib.auth.middleware.AuthenticationMiddleware', |
203
|
|
|
'django.contrib.auth.middleware.RemoteUserMiddleware', |
204
|
|
|
'django.contrib.messages.middleware.MessageMiddleware', |
205
|
|
|
'social_django.middleware.SocialAuthExceptionMiddleware', |
206
|
|
|
'opensubmit.middleware.CourseRegister' |
207
|
|
|
) |
208
|
|
|
ROOT_URLCONF = 'opensubmit.urls' |
209
|
|
|
WSGI_APPLICATION = 'opensubmit.wsgi.application' |
210
|
|
|
INSTALLED_APPS = ( |
211
|
|
|
'django.contrib.auth', |
212
|
|
|
'django.contrib.contenttypes', |
213
|
|
|
'django.contrib.sessions', |
214
|
|
|
'django.contrib.messages', |
215
|
|
|
'django.contrib.staticfiles', |
216
|
|
|
'social_django', |
217
|
|
|
'bootstrapform', |
218
|
|
|
'grappelli.dashboard', |
219
|
|
|
'grappelli', |
220
|
|
|
'django.contrib.admin', |
221
|
|
|
# 'django.contrib.admin.apps.SimpleAdminConfig', |
222
|
|
|
'opensubmit.app.OpenSubmitConfig', |
223
|
|
|
) |
224
|
|
|
|
225
|
|
|
LOG_FILE = config.get('server', 'LOG_FILE') |
226
|
|
|
|
227
|
|
|
LOGGING = { |
228
|
|
|
'version': 1, |
229
|
|
|
'disable_existing_loggers': False, |
230
|
|
|
'filters': { |
231
|
|
|
'require_debug_false': { |
232
|
|
|
'()': 'django.utils.log.RequireDebugFalse' |
233
|
|
|
}, |
234
|
|
|
'require_debug_true': { |
235
|
|
|
'()': 'django.utils.log.RequireDebugTrue' |
236
|
|
|
}, |
237
|
|
|
}, |
238
|
|
|
'formatters': { |
239
|
|
|
'verbose': { |
240
|
|
|
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s" |
241
|
|
|
}, |
242
|
|
|
'simple': { |
243
|
|
|
'format': '%(levelname)s %(message)s' |
244
|
|
|
}, |
245
|
|
|
}, |
246
|
|
|
'handlers': { |
247
|
|
|
'mail_admins': { |
248
|
|
|
'level': 'ERROR', |
249
|
|
|
'filters': ['require_debug_false'], |
250
|
|
|
'class': 'django.utils.log.AdminEmailHandler' |
251
|
|
|
}, |
252
|
|
|
'console': { |
253
|
|
|
'level': 'DEBUG', |
254
|
|
|
'filters': ['require_debug_true'], |
255
|
|
|
'class': 'logging.StreamHandler' |
256
|
|
|
}, |
257
|
|
|
'file': { |
258
|
|
|
'level': 'DEBUG', |
259
|
|
|
'class': 'logging.FileHandler', |
260
|
|
|
'formatter': 'verbose', |
261
|
|
|
'filename': LOG_FILE |
262
|
|
|
} |
263
|
|
|
}, |
264
|
|
|
'loggers': { |
265
|
|
|
'django.request': { |
266
|
|
|
'handlers': ['mail_admins', 'file'], |
267
|
|
|
'level': 'ERROR', |
268
|
|
|
'propagate': True, |
269
|
|
|
}, |
270
|
|
|
'OpenSubmit': { |
271
|
|
|
'handlers': ['console', 'file'], |
272
|
|
|
'level': 'DEBUG', |
273
|
|
|
'propagate': True, |
274
|
|
|
}, |
275
|
|
|
'social': { |
276
|
|
|
'handlers': ['console', 'file'], |
277
|
|
|
'level': 'DEBUG', |
278
|
|
|
'propagate': True, |
279
|
|
|
}, |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
LOGIN_GOOGLE = config.getboolean('login', 'LOGIN_GOOGLE') |
284
|
|
|
LOGIN_OPENID = config.getboolean('login', 'LOGIN_OPENID') |
285
|
|
|
LOGIN_GITHUB = config.getboolean('login', 'LOGIN_GITHUB') |
286
|
|
|
LOGIN_TWITTER = config.getboolean('login', 'LOGIN_TWITTER') |
287
|
|
|
LOGIN_SHIB = config.getboolean('login', 'LOGIN_SHIB') |
288
|
|
|
|
289
|
|
|
AUTHENTICATION_BACKENDS = ( |
290
|
|
|
'django.contrib.auth.backends.ModelBackend', |
291
|
|
|
) |
292
|
|
|
|
293
|
|
|
if LOGIN_GOOGLE: |
294
|
|
|
AUTHENTICATION_BACKENDS += ('social_core.backends.google.GoogleOAuth2',) |
295
|
|
|
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = config.get("login", "LOGIN_GOOGLE_OAUTH_KEY") |
296
|
|
|
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = config.get("login", "LOGIN_GOOGLE_OAUTH_SECRET") |
297
|
|
|
|
298
|
|
|
if LOGIN_TWITTER: |
299
|
|
|
AUTHENTICATION_BACKENDS += ('social_core.backends.twitter.TwitterOAuth',) |
300
|
|
|
SOCIAL_AUTH_TWITTER_KEY = config.get("login", "LOGIN_TWITTER_OAUTH_KEY") |
301
|
|
|
SOCIAL_AUTH_TWITTER_SECRET = config.get("login", "LOGIN_TWITTER_OAUTH_SECRET") |
302
|
|
|
|
303
|
|
|
if LOGIN_GITHUB: |
304
|
|
|
AUTHENTICATION_BACKENDS += ('social_core.backends.github.GithubOAuth2',) |
305
|
|
|
SOCIAL_AUTH_GITHUB_KEY = config.get("login", "LOGIN_GITHUB_OAUTH_KEY") |
306
|
|
|
SOCIAL_AUTH_GITHUB_SECRET = config.get("login", "LOGIN_GITHUB_OAUTH_SECRET") |
307
|
|
|
|
308
|
|
|
if LOGIN_OPENID: |
309
|
|
|
AUTHENTICATION_BACKENDS += ('opensubmit.social.open_id.OpenIdAuth',) |
310
|
|
|
LOGIN_DESCRIPTION = config.get('login', 'LOGIN_DESCRIPTION') |
311
|
|
|
OPENID_PROVIDER = config.get('login', 'OPENID_PROVIDER') |
312
|
|
|
|
313
|
|
|
if LOGIN_SHIB: |
314
|
|
|
AUTHENTICATION_BACKENDS += ('opensubmit.social.apache.ModShibAuth',) |
315
|
|
|
LOGIN_SHIB_DESCRIPTION = config.get('login', 'LOGIN_SHIB_DESCRIPTION') |
316
|
|
|
|
317
|
|
|
AUTHENTICATION_BACKENDS += ('opensubmit.social.lti.LtiAuth',) |
318
|
|
|
|
319
|
|
|
SOCIAL_AUTH_URL_NAMESPACE = 'social' |
320
|
|
|
SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['next',] |
321
|
|
|
SOCIAL_AUTH_PIPELINE = ( |
322
|
|
|
'social_core.pipeline.social_auth.social_details', |
323
|
|
|
'social_core.pipeline.social_auth.social_uid', |
324
|
|
|
'social_core.pipeline.social_auth.auth_allowed', |
325
|
|
|
'social_core.pipeline.social_auth.social_user', |
326
|
|
|
'social_core.pipeline.user.get_username', |
327
|
|
|
'social_core.pipeline.social_auth.associate_by_email', # Transition for existing users |
328
|
|
|
'social_core.pipeline.user.create_user', |
329
|
|
|
'social_core.pipeline.social_auth.associate_user', |
330
|
|
|
'social_core.pipeline.social_auth.load_extra_data', |
331
|
|
|
'social_core.pipeline.user.user_details' |
332
|
|
|
) |
333
|
|
|
|
334
|
|
|
JOB_EXECUTOR_SECRET = config.get("executor", "SHARED_SECRET") |
335
|
|
|
assert(JOB_EXECUTOR_SECRET is not "") |
336
|
|
|
|
337
|
|
|
GRAPPELLI_ADMIN_TITLE = "OpenSubmit" |
338
|
|
|
GRAPPELLI_SWITCH_USER = True |
339
|
|
|
GRAPPELLI_INDEX_DASHBOARD = { |
340
|
|
|
'opensubmit.admin.teacher_backend': 'opensubmit.dashboard.TeacherDashboard' |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
assert(not config.has_section('overrides')) # factored out |
344
|
|
|
|