Skip to content
This repository has been archived by the owner on Dec 23, 2021. It is now read-only.

Commit

Permalink
Merge pull request #31 from mitodl/rc/v0.1.0
Browse files Browse the repository at this point in the history
First release
  • Loading branch information
carsongee committed Apr 22, 2015
2 parents 66f7828 + 10382d5 commit 52bfbbb
Show file tree
Hide file tree
Showing 36 changed files with 2,339 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .bowerrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"directory": "lmod_proxy/static/bower/"
}


11 changes: 11 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[run]

branch = True

[paths]

source = lmod_proxy

[report]
exclude_lines =
pragma: no cover
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,12 @@ docs/_build/

# PyBuilder
target/

node_modules/

# App specific
.cert.pem
.htpasswd

# Heroku
.env
18 changes: 18 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
language: python
python:
- 2.7
install:
- pip install -r requirements.txt
- pip install coveralls
script:
- python setup.py test --coverage --pep8 --flakes
- coverage run --source=lmod_proxy setup.py test
after_success: coveralls
deploy:
provider: heroku
api_key:
secure: gHKG6WIKW/MHqTkhdEKZO47SL6Bw1w6kHct/zSwO9fcNA6lLngoTXgJ0AgXdXYoIn5suHgOTM5E1TFRbJ/63tCgGLJgZHkqa0CBZHrp2hK2E2K6rRmGq0QawRfu2QIFN+97VhtLB0pbXzsKYUJRFRWBrLoi62SalxUIJXj8MxJ8=
app:
master: lmod-proxy-ci
on:
repo: mitodl/lmod_proxy
685 changes: 662 additions & 23 deletions LICENSE

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include README.rst
include test_requirements.txt
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: uwsgi uwsgi.ini
71 changes: 71 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
lmod_proxy
==========
.. image:: https://img.shields.io/travis/mitodl/lmod_proxy.svg
:target: https://travis-ci.org/mitodl/lmod_proxy
.. image:: https://img.shields.io/coveralls/mitodl/lmod_proxy.svg
:target: https://coveralls.io/r/mitodl/lmod_proxy
.. image:: https://img.shields.io/github/issues/mitodl/lmod_proxy.svg
:target: https://github.com/mitodl/lmod_proxy/issues
.. image:: https://img.shields.io/badge/license-AGPLv3-blue.svg
:target: https://github.com/mitodl/lmod_proxy/blob/master/license

|Deploy|

.. |Deploy| image:: https://www.herokucdn.com/deploy/button.png
:target: https://heroku.com/deploy

Flask application for proxying requests to the MIT Learning Modules
API from edx-platform.


Quick Start
===========

- Download the latest tar ball or clone from github.
- Run ``pip install -r requirements.txt``
- Make sure you have ``htpasswd`` available/installed
- Run ``htpasswd -c ~/.htpasswd <your username>``
- Create environment variable with path to password file: ``export LMODP_HTPASSWD_PATH=~/.htpasswd``
- Run ``lmod_proxy``
- Go to https://localhost:5000/ and use the form there to try things out

You will also likely need to customize your settings to be able to do
authentication to LMod via a proper certificate. To do that,
determine the path to your certificate and export that path for the
configuration with a configuration environment variable: ``export
LMODP_CERT=/path/to/mycert.pem``. The certificate needs to be a plain
(no passphrase) base64 encoded file with both your certificate and
private key from MIT.


Running on Heroku
=================

There is an included Procfile to run the application in heroku, to do
so just create a heroku app, configure the appropriate environment
variables in heroku via ``heroku config:set LMODP_...=...``. The one
catch being that your certificate file and HTPASSWD files probably
shouldn't be checked in with the repository on heroku, so you can
actually copy your entire HTPASSWD file into the ``LMODP_HTPASSWD``
variable and your certificate into ``LMODP_CERT_STRING`` variable.


Configuring edX Platform
========================

To configure your edX platform to use this as your remote gradebook
endpoint, you need to configure a few things, first you need to enable
the feature and point it at the URL of your lmod_proxy server. Adding
something like: ``FEATURES['REMOTE_GRADEBOOK_URL'] =
'https://<htpasswd_user>:<htpasswd_password>@myapp.herokuapp.com/edx_grades'``
to your config will enable the feature, and then in your course, you
will just need to specify the GradeBook UUID to point at in your
advanced settings with something like:

.. code-block:: json
"remote_gradebook": {
"name": "STELLAR:/project/mitxdemosite",
"section": "devops01"
}
23 changes: 23 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "LMod Proxy",
"description": "Proxies grades from Open edX platform to MIT campus Learning Modules",
"keywords": [
"flask",
"python",
"MIT",
"Learning Modules",
"Open edX",
"Grades"
],
"website": "http://engineering.odl.mit.edu",
"repository": "https://github.com/mitodl/lmod_proxy",
"success_url": "/edx_grades",
"env": {
"LMODP_HTPASSWD": {
"description": "Pasted contents of an htpasswd file"
},
"LMODP_CERT_STRING": {
"description": "Pasted contents of an unencrypted base64 key and certificate from MIT."
}
}
}
22 changes: 22 additions & 0 deletions bower.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "lmod_proxy",
"version": "0.1.0",
"homepage": "https://github.com/mitodl/lmod_proxy",
"authors": [
"ODL Engineering <odl-engineering@mit.edu>"
],
"description": "Flask application for proxying requests to the MIT Learning Modules API from edx-platform",
"license": "BSD",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"furtive": "~2.0.1",
"jquery2": "~2.0.0"
}
}
30 changes: 30 additions & 0 deletions lmod_proxy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""lmod_proxy
Flask application for proxying requests to the MIT Learning Modules
API from edx-platform
"""
import os.path
from pkg_resources import get_distribution, DistributionNotFound


def _get_version():
"""Grab version from pkg_resources"""
# pylint: disable=no-member
try:
dist = get_distribution(__project__)
# Normalize case for Windows systems
dist_loc = os.path.normcase(dist.location)
here = os.path.normcase(os.path.abspath(__file__))
if not here.startswith(
os.path.join(dist_loc, __project__)
):
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
return 'Please install this project with setup.py'
else:
return dist.version

__project__ = 'lmod_proxy'
__version__ = _get_version()
57 changes: 57 additions & 0 deletions lmod_proxy/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""Basic authentication decorator for views"""
from functools import wraps
import logging

from flask import current_app, request, Response

log = logging.getLogger(__name__) # pylint: disable=invalid-name


def check_basic_auth(username, password):
"""
This function is called to check if a username /
password combination is valid via the htpasswd file.
"""
valid = current_app.config['users'].check_password(username, password)
if not valid:
log.warn('Invalid login from %s', username)
valid = False
return (
valid,
username
)


def auth_failed():
"""
Sends a 401 response that enables basic auth
"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials',
401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)


def requires_auth(func):
"""
Decorator function with basic and token authentication handler
"""
@wraps(func)
def decorated(*args, **kwargs):
"""
Actual wrapper to run the auth checks.
"""
basic_auth = request.authorization
is_valid = False
if basic_auth:
is_valid, user = check_basic_auth(
basic_auth.username, basic_auth.password
)
if not is_valid:
return auth_failed()
kwargs['user'] = user
return func(*args, **kwargs)
return decorated
21 changes: 21 additions & 0 deletions lmod_proxy/cmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""Command line debug start of flask application"""
import os

from lmod_proxy import config


def run_server():
"""Debug running of the application via flask's app reloader.
Do not use this command to run in production. A WSGI container like
uwsgi or gunicorn should be used.
"""
port = int(os.environ.get('LMODP_PORT', 5000))
host = os.environ.get('LMODP_HOST', 'localhost')

# Debug configuration settings
config.FLASK_LOG_LEVEL = 'DEBUG'

from lmod_proxy.web import app
app.run(debug=True, host=host, port=port)
82 changes: 82 additions & 0 deletions lmod_proxy/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""Configuration of flask application via environment, or file"""
import os

import yaml

CONFIG_PATHS = [
os.environ.get('LMODP_CONFIG', ''),
os.path.join(os.getcwd(), 'lmod_proxy.yml'),
os.path.join(os.path.expanduser('~'), 'lmod_proxy.yml'),
'/etc/lmod_proxy.yml',
]

CONFIG_KEYS = {
# Path to base64 encoded, un-passphrased, key and certificate
# combined file
'LMODP_CERT': 'ocw.app.mit.edu-key-and-cert.pem',

# The base64 encoded string of the certificate as opposed to the
# path. This will trigger the app to write out the certificate on
# startup for environments that are best configured via variables
# only like Heroku.
'LMODP_CERT_STRING': '',

# Base URL for which the gradebook API lives and accepts
# certificate authentication
'LMODP_URLBASE': 'https://learning-modules.mit.edu:8443/',

# Any value other than '' or unset enables grade approval in
# Learning Modules so that instructors do no have to do it
# manually for all grades posted in this instance.
'LMODP_APPROVE_GRADES': None,

# Direct path to apache htpasswd file to use for basic auth
'LMODP_HTPASSWD_PATH': '.htpasswd',

# Setting that actually contains the strings of the htpasswd file
# for situations like heroku. If set it will write out the string
# to a file for reading on startup.
'LMODP_HTPASSWD': None,

# Logging level
'FLASK_LOG_LEVEL': 'INFO',

# Disable WTF CSRF protection
'WTF_CSRF_ENABLED': False,
}


def _configure():
"""Configure the application by trying config file and overriding with
environment variables.
"""
config_file_path = None
fallback_config = {}
for config_path in CONFIG_PATHS:
if os.path.isfile(config_path):
config_file_path = config_path
break
if config_file_path:
with open(config_file_path) as config_file:
fallback_config = yaml.load(config_file)

configuration = {}
for key, default_value in CONFIG_KEYS.items():
configuration[key] = os.environ.get(
key, fallback_config.get(key, default_value)
)

if configuration['LMODP_HTPASSWD']:
configuration['LMODP_HTPASSWD_PATH'] = os.path.abspath('.htpasswd')
with open(configuration['LMODP_HTPASSWD_PATH'], 'w') as wfile:
wfile.write(configuration['LMODP_HTPASSWD'])

if configuration['LMODP_CERT_STRING']:
configuration['LMODP_CERT'] = os.path.abspath('.cert.pem')
with open(configuration['LMODP_CERT'], 'w') as wfile:
wfile.write(configuration['LMODP_CERT_STRING'])

return configuration

globals().update(_configure())

0 comments on commit 52bfbbb

Please sign in to comment.