django-environ - Managing LDAP DN in .env - python

I have a django web app, with a middleware that intercepts incoming requests, extracts user details (added to header by upstream middleware) in the request header, and checks if user is authorized to access the page if user is member of a distribution group.
I'm using django-environ to manage my environment variables so i can modify the list of DL Groups which can access my page without changing the code.
# in Middleware, only included important part of code
from django.conf import settings
MINIMAL_MEMBERSHIP = settings.AUTHORIZED_MEMBERSHIP_REQUIREMENT # This should pass in as a list
server_object = Server(LDAP_SERVER)
conn = Connection(server_object, LDAP_USER, LDAP_PASS, client_strategy=SAFE_SYNC, auto_bind=True)
status, result, response, _ = conn.search(
search_base=requester_dn,
search_filter = '(objectClass=User)',
attributes = ['memberOf']
)
authorized = False
requester_membership_list = response[0]['raw_attributes']['memberOf']
for membership in requester_membership_list:
ad_group_name = membership.decode('utf-8')
if ad_group_name in MINIMAL_MEMBERSHIP:
authorized = True
break
# In settings.py
AUTHORIZED_MEMBERSHIP_REQUIREMENT = env.list('AUTHORIZED_MEMBERSHIP_REQUIREMENT')
# In .env
AUTHORIZED_MEMBERSHIP_REQUIREMENT="CN=Virtualisation team,OU=Distribution Group,OU=Exchange,OU=APPS,DC=xxx,DC=xxx,DC=xxx,DC=com",
According to django-environ, you can read .env as a list like
# .env
LIST_ENV=one,two,three,four
# settings.py
LIST_ENV=env.list(LIST_ENV)
print(LIST_ENV) # outputs ['one', 'two', 'three', 'four']
But understandably ldap DN format will break this as a full DN is already delimited by commas, so:
# .env
DN_LIST="CN=1,OU=1,OU=1,OU=1,DC=xxx,DC=xxx,DC=xxx,DC=com","CN=2,OU=2,OU=2,OU=2,DC=xxx,DC=xxx,DC=xxx,DC=com"
# settings.py
DN_LIST=env.list(DN_LIST)
# Actual Behavior
print(DN_LIST)
# DN_LIST = ['CN=1', 'OU=1', 'OU=1', ...]
# len(DN_LIST) will output 16
I would like achieve this:
# Behavior i want
print(DN_LIST)
# DN_LIST = ["CN=1,OU=1,OU=1,OU=1,DC=xxx,DC=xxx,DC=xxx,DC=com","CN=2,OU=2,OU=2,OU=2,DC=xxx,DC=xxx,DC=xxx,DC=com"]
# len(DN_LIST) will output 2
Is there anyway to do this, or maybe any alternatives to manage the list from .env without modifying the code if i need to add/remove new groups?
Thank you

Sometimes taking a night off provides a simple solution to an otherwise simple problem.. heh.
Decided to take the DN List in .env as a string and have the DNs delimited by something (e.g. ;) and manually split/process it into a list.
# .env
AUTHORIZED_MEMBERSHIP_REQUIREMENT=CN=1,OU=1,OU=1,OU=1,DC=xxx,DC=xxx,DC=xxx,DC=com;CN=2,OU=2,OU=2,OU=2,DC=xxx,DC=xxx,DC=xxx,DC=com
# .settings.py
AUTHORIZED_MEMBERSHIP_REQUIREMENT = env.str('AUTHORIZED_MEMBERSHIP_REQUIREMENT').split(';')

This is about how you read the shell environment variable (.env) into python
DN_LIST="CN=1,OU=1,OU=1,OU=1,DC=xxx" ## instead this
DN_LIST_NEW='["CN=1,OU=1,OU=1,OU=1,DC=xxx","CN=2,OU=2,OU=2,OU=2,DC=xxx"]' ## please you this
Off-topic: define these DN in .env means if we want to change the DN values, we have to change in .env and **restart/deploy** the application , which is not good. You should think about put these DN values into a table in database

Related

How to Mask Twilio/Python Variable

I have a Python project where I am using a Twilio number (201-282-1111), but I do not want this private number to be in my public Git repository. I could make a new repo and just not include the values, but I would like it be easy enough for non techie people to use my script.
My question is, what can I do to mask the fake number provided below? Also, I have Twilio account_sid and auth_token that I would like to mask as well.
client.sms.messages.create(
body = "Your song was not found",
to = phoneNum,
from_ = "+2012821111")
account_sid = "XX1a116061fe67df9ab9eb0bc3c7ed0111"
auth_token = "1x11bc1b6555c52a93762f46d45861xx"
client = TwilioRestClient(account_sid,auth_token)
Use a config file. On one of my projects, this is what I did:
Install ConfigParser.
Create a file named config (or another name, your choice) that looks like this:
[Config]
account_sid=<your sid>
auth_token=<token>
phone_number=<your number>
Edit your .gitignore file and add config to it, this will keep Git from committing your config file to the repository
[optional] Add a config.sample file, which is a copy of the config file but with your values set to defaults or dummy values. Commit this one to Git. This helps other people set up your app later - all they have to do is copy the file to config and put in their credentials.
To access the config values, use this code:
config = ConfigParser.RawConfigParser()
config.read('config')
try:
your_value = config.get("Config", "your_value")
print your_value
except ConfigParser.NoOptionError:
print "'your_value' isn't defined in the config file!"
Another option would be to use an environment variable or to simply ask the user for their credentials when your app starts.

Reusing paste config entry

I have the following Pyramid .ini file:
[DEFAULT]
redis.host = localhost
redis.port = 6379
redis.db = 0
[app:main]
...
# beaker session
session.type = redis
session.url = localhost:6379
In the app:main section's session.url I want to use what's defined under DEFAULT section's redis.host and redis.port.
In my understanding everything under DEFAULT section is global and is passed to other sections. But if I want to reuse a settings from DEFAULT and assign it a different name under other sections how do I do that?
I'm looking at the same way I can reference section entry in buildout .cfg files using ${<section name>:<entry>}.
session.url = %(redis.host)s:%(redis.port)s
Should do the trick.

Build Automation for Django

I'm pretty new to doing sysadmin stuff for my development and to the django framework. I want to have a different username/password for my local dev station and to my production environment.
I'm using dotcloud as the server. I can write a post install script (in python, bash, whatever) and it will execute it on every new push.
However I don't know how to go about this. Do I need to write this myself? is there a python/django build automation tools that will help me with that?
Clarification: how can I change debug=false in settings.py to true just on the server?
The django standard way is to use an environmanet variable DJANGO_SETTINGS_MODULE. Point it to different settings and let both import a common settings module for common things:
# settings_production.py
from settings_common import *
DEBUG = False
DATABASES = {...}
# settings_development.py
from settings_common import *
DEBUG = True
DATABASES = {...}
# settings_common.py
INSTALLED_APPS = (...) # etc
You can also use an alternative strategy of using one main settings and import names from another one depending on some system condition, like getting os.platform.node() or socket.gethostname() and switch over that value (or part of it).
reversed_hostname_parts = socket.gethostname().split('.').reverse()
host_specific = {
('com', 'dotcloud'): 'production',
('local'): 'dev',
}
for index in range(len(reversed_hostname_parts)):
identifier = tuple(reversed_hostname_parts[:index+1])
if identifier in host_specific:
extra_settings = host_specific[identifier]
break
else: # executed when the loop has not been `break`ed
extra_settings = 'dev' # any default value
if extra_settings == 'dev':
from development_settings import *
elif extra_settings == 'production':
from production_settings import *
EDIT: added link
See https://code.djangoproject.com/wiki/SplitSettings for other strategies.
I usually import my development settings at the end of production settings.py, if my project is residing on a local directory structure.
You can also store your DB settings and other settings that are different in production and development in a separate file and remove them from your SVN, Git of whatever you use.
Just add this at the end of your settings.py:
try:
from myapp.specific_settings import *
except ImportError:
pass
In this case, specific_settings will be different in production and development environment.
If you want to dynamically choose between development and production servers use this at the end of settings:
import os
directory = os.path.dirname(__file__)
if directory == '/home/yourname/development/':
from myapp.development_settings import *
else:
from myapp.production_settings import *
Note that I wrote this on top of my head and there might be some bugs in it. I will verify that when I go home.

Link generator using django or any python module

I want to generate for my users temporary download link.
Is that ok if i use django to generate link using url patterns?
Could it be correct way to do that. Because can happen that I don't understand some processes how it works. And it will overflow my memory or something else. Some kind of example or tools will be appreciated. Some nginx, apache modules probably?
So, what i wanna to achieve is to make url pattern which depend on user and time. Decript it end return in view a file.
A simple scheme might be to use a hash digest of username and timestamp:
from datetime import datetime
from hashlib import sha1
user = 'bob'
time = datetime.now().isoformat()
plain = user + '\0' + time
token = sha1(plain)
print token.hexdigest()
"1e2c5078bd0de12a79d1a49255a9bff9737aa4a4"
Next you store that token in a memcache with an expiration time. This way any of your webservers can reach it and the token will auto-expire. Finally add a Django url handler for '^download/.+' where the controller just looks up that token in the memcache to determine if the token is valid. You can even store the filename to be downloaded as the token's value in memcache.
Yes it would be ok to allow django to generate the urls. This being exclusive from handling the urls, with urls.py. Typically you don't want django to handle the serving of files see the static file docs[1] about this, so get the notion of using url patterns out of your head.
What you might want to do is generate a random key using a hash, like md5/sha1. Store the file and the key, datetime it's added in the database, create the download directory in a root directory that's available from your webserver like apache or nginx... suggest nginx), Since it's temporary, you'll want to add a cron job that checks if the time since the url was generated has expired, cleans up the file and removes the db entry. This should be a django command for manage.py
Please note this is example code written just for this and not tested! It may not work the way you were planning on achieving this goal, but it works. If you want the dl to be pw protected also, then look into httpbasic auth. you can generate and remove entries on the fly in a httpd.auth file using htpasswd and the subprocess module when you create the link or at registration time.
import hashlib, random, datetime, os, shutil
# model to hold link info. has these fields: key (charfield), filepath (filepathfield)
# datetime (datetimefield), url (charfield), orgpath (filepathfield of the orignal path
# or a foreignkey to the files model.
from models import MyDlLink
# settings.py for the app
from myapp import settings as myapp_settings
# full path and name of file to dl.
def genUrl(filepath):
# create a onetime salt for randomness
salt = ''.join(['{0}'.format(random.randrange(10) for i in range(10)])
key = hashlib('{0}{1}'.format(salt, filepath).hexdigest()
newpath = os.path.join(myapp_settings.DL_ROOT, key)
shutil.copy2(fname, newpath)
newlink = MyDlink()
newlink.key = key
newlink.date = datetime.datetime.now()
newlink.orgpath = filepath
newlink.newpath = newpath
newlink.url = "{0}/{1}/{2}".format(myapp_settings.DL_URL, key, os.path.basename(fname))
newlink.save()
return newlink
# in commands
def check_url_expired():
maxage = datetime.timedelta(days=7)
now = datetime.datetime.now()
for link in MyDlink.objects.all():
if(now - link.date) > maxage:
os.path.remove(link.newpath)
link.delete()
[1] http://docs.djangoproject.com/en/1.2/howto/static-files/
It sounds like you are suggesting using some kind of dynamic url conf.
Why not forget your concerns by simplifying and setting up a single url that captures a large encoded string that depends on user/time?
(r'^download/(?P<encrypted_id>(.*)/$', 'download_file'), # use your own regexp
def download_file(request, encrypted_id):
decrypted = decrypt(encrypted_id)
_file = get_file(decrypted)
return _file
A lot of sites just use a get param too.
www.example.com/download_file/?09248903483o8a908423028a0df8032
If you are concerned about performance, look at the answers in this post: Having Django serve downloadable files
Where the use of the apache x-sendfile module is highlighted.
Another alternative is to simply redirect to the static file served by whatever means from django.

Django localeURL when WSGIScriptAlias is /PREFIX

Introduction
I Got a question about localeURL usage.
Everything works great for me with url like this :
http://www.example.com/
If I type http://www.example.com/ in address bar, it turns correctly in http://www.example.com/en/ for example.
If I use the view change_locale, it's also all right (ie change www.example.com/en/ in www.example.com/fr/).
problem
But my application use apache as server, with mod_wsgi. The httpd.conf script contains this line :
WSGIScriptAlias /MY_PREFIX /path/to/django/app/apache/django.wsgi
that gives url like this :
http://www.example.com/MY_PREFIX/
If I type http://www.example.com/MY_PREFIX/ in the address bar, the adress turns into http://www.example.com/en/ when the expected result should be http://www.example.com/MY_PREFIX/en/
The same problem occurred with the change_locale view. I modified this code in order to manage this prefix (store in settings.SERVER_PREFIX):
def change_locale(request) :
"""
Redirect to a given url while changing the locale in the path
The url and the locale code need to be specified in the
request parameters.
O. Rochaix; Taken from localeURL view, and tuned to manage :
- SERVER_PREFIX from settings.py
"""
next = request.REQUEST.get('next', None)
if not next:
next = request.META.get('HTTP_REFERER', None)
if not next:
next = settings.SERVER_PREFIX + '/'
next = urlsplit(next).path
prefix = False
if settings.SERVER_PREFIX!="" and next.startswith(settings.SERVER_PREFIX) :
prefix = True
next = "/" + next.lstrip(settings.SERVER_PREFIX)
_, path = utils.strip_path (next)
if request.method == 'POST':
locale = request.POST.get('locale', None)
if locale and check_for_language(locale):
path = utils.locale_path(path, locale)
if prefix :
path = settings.SERVER_PREFIX + path
response = http.HttpResponseRedirect(path)
return response
with this customized view, i'm able to correctly change language, but i'm not sure that's the right way of doing stuff.
Question
when, in httpd.conf you use WSGIScriptAlias with /PREFIX (ie "/Blog"), do we need, on python side to use a variable (here settings.SERVER_PREFIX) that match WSGIScriptAlias ? i use it for MEDIA_URL and other stuff, but maybe there is some configuration to do in order to make it work "automatically" without having to manage this on python side
Do you think that this customized view (change_locale) is the right way to manage this issue ? Or is there some kind of automagic stuff as for 1. ?
It doesn't solve the problem if I type the address (http://www.example.com/MY_PREFIX/) in address bar. If customization is the way to go, i will change this as well, but I think there is a better solution!
You should not be hard wiring SERVER_PREFIX in settings. The mount prefix for the site is available as SCRIPT_NAME in the WSGI environ dictionary. Thus from memory is available as request.META.get('SCRIPT_NAME').
try this (I am not sure whether it will work though):
WSGIScriptAliasMatch ^/MY_PREFIX(/.*)?$ /path/to/django/app/apache/django.wsgi$1
basically the idea s to make django believe that there is no prefix
but you need to make sure django emits the correct URLs in its HTML output.

Categories