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