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