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