Passed
Push — master ( 7e1d33...a505d5 )
by Peter
01:26
created

ensure_slash_from_config()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
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