├── utils ├── __init__.py ├── billing.py └── encryption.py ├── vault ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0001_initial.py │ ├── 0004_auto__add_field_credentialgroup_name__add_field_credentialgroup_descri.py │ ├── 0003_auto__del_field_credential_group.py │ ├── 0005_auto__add_field_credential_uuid__add_field_credentialgroup_uuid.py │ ├── 0006_auto__del_field_credential_key__add_field_credential_username__add_fie.py │ └── 0002_auto__add_credentialgroup__del_field_credential_owner__add_field_crede.py ├── tests.py ├── admin.py ├── forms.py ├── urls.py ├── models.py ├── templates │ └── vault │ │ ├── index.html │ │ └── group.html └── views.py ├── accounts ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_auto__add_field_userprofile_is_pro.py │ ├── 0001_initial.py │ ├── 0003_auto__add_field_userprofile_pro_join_date.py │ ├── 0004_auto__add_field_userprofile_customer_id.py │ ├── 0005_auto__chg_field_userprofile_customer_id.py │ └── 0006_auto__add_field_userprofile_activation_code.py ├── templates │ └── accounts │ │ ├── details.html │ │ ├── login.html │ │ ├── signup.html │ │ └── activate.html ├── tests.py ├── urls.py ├── forms.py ├── models.py └── views.py ├── locksmith ├── __init__.py ├── middleware │ ├── __init__.py │ └── threadlocal.py ├── templatetags │ ├── __init__.py │ └── locksmith.py ├── urls.py ├── admin.py ├── wsgi.py ├── context_processors.py ├── views.py ├── api_v1.py └── settings.py ├── static ├── css │ ├── bg.css │ ├── fonts │ │ ├── entypo.eot │ │ ├── entypo.ttf │ │ ├── entypo.woff │ │ ├── entypo-social.eot │ │ ├── entypo-social.ttf │ │ ├── entypo-social.woff │ │ ├── entypo.svg │ │ └── entypo-social.svg │ ├── fonts.css │ ├── app.css │ └── bootstrap-responsive.min.css ├── img │ ├── bg.png │ ├── lock.png │ ├── cards.png │ ├── favicon.ico │ ├── group.png │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── libs │ └── zc.swf └── js │ ├── app.js │ ├── zc.min.js │ └── holder.js ├── .gitignore ├── templates ├── 404.html ├── index.html ├── about.html └── base.html ├── wsgi.py ├── manage.py ├── requirements.txt ├── license └── readme.md /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vault/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locksmith/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vault/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locksmith/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locksmith/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/css/bg.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('/static/img/bg.png'); 3 | } 4 | -------------------------------------------------------------------------------- /static/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/bg.png -------------------------------------------------------------------------------- /static/img/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/lock.png -------------------------------------------------------------------------------- /static/libs/zc.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/libs/zc.swf -------------------------------------------------------------------------------- /static/img/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/cards.png -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/group.png -------------------------------------------------------------------------------- /static/css/fonts/entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/css/fonts/entypo.eot -------------------------------------------------------------------------------- /static/css/fonts/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/css/fonts/entypo.ttf -------------------------------------------------------------------------------- /static/css/fonts/entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/css/fonts/entypo.woff -------------------------------------------------------------------------------- /static/css/fonts/entypo-social.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/css/fonts/entypo-social.eot -------------------------------------------------------------------------------- /static/css/fonts/entypo-social.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/css/fonts/entypo-social.ttf -------------------------------------------------------------------------------- /static/css/fonts/entypo-social.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/css/fonts/entypo-social.woff -------------------------------------------------------------------------------- /static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | *.swo 4 | *.un* 5 | *.db 6 | local_settings.py 7 | *.log 8 | newrelic.ini 9 | *DS_Store* 10 | -------------------------------------------------------------------------------- /static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ehazlett/locksmith/HEAD/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block main_content %} 5 |
{% trans 'We are unable to find that page.' %}
6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | from locksmith import wsgi 2 | #import newrelic.agent 3 | application = wsgi.application 4 | 5 | #newrelic.agent.initialize('newrelic.ini') 6 | #app = newrelic.agent.wsgi_application()(app) 7 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "locksmith.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /vault/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /static/css/fonts/entypo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /static/css/fonts/entypo-social.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.4.3 2 | MySQL-python==1.2.3 3 | South==0.7.6 4 | boto==2.8.0 5 | django-bcrypt==0.9.2 6 | django-forms-bootstrap==2.0.3.post1 7 | django-redis-cache==0.9.7 8 | django-social-auth==0.7.19 9 | django-storages==1.1.6 10 | django-tastypie==0.9.12 11 | hiredis==0.1.1 12 | httplib2==0.7.7 13 | mimeparse==0.1.3 14 | oauth2==1.5.211 15 | py-bcrypt==0.2 16 | pycrypto==2.6 17 | python-dateutil==2.1 18 | python-memcached==1.48 19 | python-openid==2.2.5 20 | raven==3.1.11 21 | redis==2.7.2 22 | requests==1.1.0 23 | simplejson==3.0.7 24 | six==1.2.0 25 | stripe==1.7.10 26 | wsgiref==0.1.2 27 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /accounts/templates/accounts/details.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | {% load bootstrap_tags %} 4 | 5 | {% block main_content %} 6 |
7 |
8 | {% csrf_token %} 9 | {{form|as_bootstrap}} 10 | {{pform|as_bootstrap}} 11 |
12 |
13 | 14 | {% trans 'Cancel' %} 15 |
16 |
17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Locksmith 2 | Locksmith is a password management application. Think of it as a lightweight 3 | open source web based 1password or keepass. 4 | 5 | Visit #locksmith-io on IRC (freenode) for questions, help, etc. 6 | 7 | Screenshots [here](https://github.com/ehazlett/locksmith/wiki/Screenshots) 8 | 9 | # Setup 10 | 11 | * `pip install -r requirements.txt` 12 | * `python manage.py syncdb --noinput` 13 | * `python manage.py migrate` 14 | * `python manage.py createsuperuser` 15 | * `python manage.py runserver` 16 | 17 | # Application Settings 18 | 19 | Edit all application settings in `settings.py`. You can also create a 20 | `local_settings.py` with override values. 21 | 22 | # License 23 | Apache License 2.0 24 | -------------------------------------------------------------------------------- /locksmith/templatetags/locksmith.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.template.defaultfilters import stringfilter 3 | from datetime import datetime 4 | from django.utils.translation import ugettext as _ 5 | from utils.encryption import decrypt 6 | 7 | register = template.Library() 8 | 9 | @register.filter(takes_context=True) 10 | @stringfilter 11 | def decrypt(context, data=None): 12 | request = context['request'] 13 | key = request.session.get('key') 14 | try: 15 | dec = decrypt(data, key) 16 | except: 17 | dec = _('access denied') 18 | return dec 19 | 20 | @register.filter 21 | def timestamp(value): 22 | try: 23 | return datetime.fromtimestamp(value) 24 | except AttributeError: 25 | return '' 26 | -------------------------------------------------------------------------------- /vault/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.contrib import admin 15 | from vault.models import CredentialGroup, Credential 16 | 17 | admin.site.register(CredentialGroup) 18 | -------------------------------------------------------------------------------- /accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth.models import User 3 | 4 | class AccountsTest(TestCase): 5 | def setUp(self): 6 | self._username = 'testuser' 7 | self._password = 'test1234' 8 | self.user = User(username=self._username) 9 | self.user.set_password(self._password) 10 | self.user.save() 11 | 12 | def test_login(self): 13 | self.client.login(username=self._username, password=self._password) 14 | resp = self.client.get('/') 15 | self.assertEqual(resp.context['user'].username, self._username) 16 | 17 | def test_logout(self): 18 | self.test_login() 19 | self.client.logout() 20 | resp = self.client.get('/') 21 | self.assertNotEqual(resp.context['user'].username, self._username) 22 | 23 | -------------------------------------------------------------------------------- /static/css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'EntypoRegular'; 3 | src: url('/static/css/fonts/entypo-webfont.eot'); 4 | src: url('/static/css/fonts/entypo.eot?#iefix') format('embedded-opentype'), 5 | url('/static/css/fonts/entypo.woff') format('woff'), 6 | url('/static/css/fonts/entypo.ttf') format('truetype'), 7 | url('/static/css/fonts/entypo.svg#EntypoRegular') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | @font-face { 12 | font-family: 'EntypoSocial'; 13 | src: url('/static/css/fonts/entypo-social.eot'); 14 | src: url('/static/css/fonts/entypo-social.eot?#iefix') format('embedded-opentype'), 15 | url('/static/css/fonts/entypo-social.woff') format('woff'), 16 | url('/static/css/fonts/entypo-social.ttf') format('truetype'), 17 | url('/static/css/fonts/entypo-social.svg#EntypoSocial') format('svg'); 18 | font-weight: normal; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /locksmith/urls.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.conf import settings 3 | from django.conf.urls import patterns, include, url 4 | from django.contrib import admin 5 | from tastypie.api import Api 6 | from locksmith.api_v1 import (UserResource, CredentialGroupResource, 7 | CredentialResource) 8 | 9 | admin.autodiscover() 10 | 11 | # api 12 | api_v1 = Api(api_name='v1') 13 | api_v1.register(UserResource()) 14 | api_v1.register(CredentialGroupResource()) 15 | api_v1.register(CredentialResource()) 16 | 17 | urlpatterns = patterns('', 18 | url(r'^$', 'locksmith.views.index', name='index'), 19 | url(r'^about/$', 'locksmith.views.about', name='about'), 20 | url(r'^admin/', include(admin.site.urls)), 21 | url(r'^api/', include(api_v1.urls)), 22 | url(r'^api/login', 'locksmith.api_v1.api_login'), 23 | url(r'^auth/(?P[^/]+)/$', 24 | 'locksmith.views.register_by_access_token'), 25 | url(r'^accounts/', include('accounts.urls')), 26 | url(r'^vault/', include('vault.urls')), 27 | url(r'', include('social_auth.urls')), 28 | ) 29 | -------------------------------------------------------------------------------- /locksmith/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.contrib import admin 15 | from django.contrib.auth.models import User 16 | from django.contrib.auth.admin import UserAdmin 17 | from accounts.models import UserProfile 18 | 19 | admin.site.unregister(User) 20 | 21 | class UserProfileInline(admin.StackedInline): 22 | model = UserProfile 23 | 24 | class UserProfileAdmin(UserAdmin): 25 | inlines = (UserProfileInline,) 26 | 27 | admin.site.register(User, UserProfileAdmin) 28 | -------------------------------------------------------------------------------- /locksmith/middleware/threadlocal.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copied from https://github.com/jedie/django-tools/blob/master/django_tools/middlewares/ThreadLocal.py 3 | try: 4 | from threading import local 5 | except ImportError: 6 | from django.utils._threading_local import local 7 | 8 | _thread_locals = local() 9 | 10 | def get_current_request(): 11 | """ returns the request object for this thead """ 12 | return getattr(_thread_locals, "request", None) 13 | 14 | def get_current_user(): 15 | """ returns the current user, if exist, otherwise returns None """ 16 | request = get_current_request() 17 | if request: 18 | return getattr(request, "user", None) 19 | 20 | def get_current_session(): 21 | """ returns the current user, if exist, otherwise returns None """ 22 | request = get_current_request() 23 | if request: 24 | return getattr(request, "session", None) 25 | 26 | class ThreadLocalMiddleware(object): 27 | """ Simple middleware that adds the request object in thread local storage.""" 28 | def process_request(self, request): 29 | _thread_locals.request = request 30 | -------------------------------------------------------------------------------- /vault/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django import forms 15 | from vault.models import CredentialGroup, Credential 16 | 17 | class CredentialGroupForm(forms.ModelForm): 18 | def __init__(self, *args, **kwargs): 19 | super(CredentialGroupForm, self).__init__(*args, **kwargs) 20 | self.fields['name'].required = True 21 | class Meta: 22 | model = CredentialGroup 23 | fields = ('name', 'description') 24 | 25 | class CredentialForm(forms.ModelForm): 26 | class Meta: 27 | model = Credential 28 | -------------------------------------------------------------------------------- /vault/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.conf.urls import patterns, url 15 | 16 | urlpatterns = patterns('vault.views', 17 | url(r'^$', 'index', name='vault.index'), 18 | url(r'^groups/(?P\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/$', 'group', 19 | name='vault.group'), 20 | url(r'^setkey/$', 'set_key', name='vault.set_key'), 21 | url(r'^lock/$', 'lock_session', name='vault.lock_session'), 22 | url(r'^genpass/$', 'random_password', name='vault.random_password'), 23 | url(r'^checksession/$', 'check_session', name='vault.check_session'), 24 | ) 25 | -------------------------------------------------------------------------------- /accounts/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.conf.urls import patterns, url 15 | 16 | urlpatterns = patterns('accounts.views', 17 | url(r'^login/$', 'login', name='accounts.login'), 18 | url(r'^logout/$', 'logout', name='accounts.logout'), 19 | url(r'^details/$', 'details', name='accounts.details'), 20 | url(r'^signup/$', 'signup', name='accounts.signup'), 21 | url(r'^confirm/(?P[\W,\w]+)/$', 'confirm', name='accounts.confirm'), 22 | url(r'^activate/$', 'activate', name='accounts.activate'), 23 | url(r'^hook/$', 'hook', name='accounts.hook'), 24 | ) 25 | -------------------------------------------------------------------------------- /locksmith/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for locksmith project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "locksmith.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | 4 | {% block main_content %} 5 |
6 |
7 |
{% trans 'Open Source Password Management' %}
8 |
9 | {% blocktrans %}Locksmith encrypts and stores your accounts in one place. No longer do you need to have applications installed or files to keep track. Access them anywhere with Locksmith.{% endblocktrans %} 10 |
11 | {% if SIGNUP_ENABLED %} 12 | 15 | {% endif %} 16 |
17 | 28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /locksmith/context_processors.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.conf import settings 15 | from django.core.cache import cache 16 | from utils.encryption import get_user_encryption_key 17 | 18 | def app_info(request): 19 | return { 20 | 'APP_NAME': getattr(settings, 'APP_NAME'), 21 | 'APP_REVISION': getattr(settings, 'APP_REVISION'), 22 | } 23 | 24 | def google_analytics_code(request): 25 | return { 'GOOGLE_ANALYTICS_CODE': getattr(settings, 'GOOGLE_ANALYTICS_CODE')} 26 | 27 | def stripe_info(request): 28 | return { 29 | 'STRIPE_API_KEY': getattr(settings, 'STRIPE_API_KEY'), 30 | 'STRIPE_PUBLISHABLE_KEY': getattr(settings, 'STRIPE_PUBLISHABLE_KEY'), 31 | } 32 | 33 | def intercom_app_id(request): 34 | return { 'INTERCOM_APP_ID': getattr(settings, 'INTERCOM_APP_ID')} 35 | 36 | def encryption_key(request): 37 | u = request.user 38 | key = get_user_encryption_key(u.username) 39 | return { 'ENCRYPTION_KEY': key } 40 | 41 | def signup_enabled(request): 42 | return { 'SIGNUP_ENABLED': getattr(settings, 'SIGNUP_ENABLED')} 43 | -------------------------------------------------------------------------------- /accounts/templates/accounts/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block main_content %} 5 |
6 |
7 | {% csrf_token %} 8 |
9 | 10 |
11 |
12 | 13 |
14 | 18 |
19 | 24 |
25 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /accounts/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django import forms 15 | from django.contrib.auth.models import User 16 | from accounts.models import UserProfile 17 | 18 | class AccountForm(forms.ModelForm): 19 | # override the default fields to force them to be required 20 | # (the django User model doesn't require them) 21 | def __init__(self, *args, **kwargs): 22 | super(AccountForm, self).__init__(*args, **kwargs) 23 | self.fields['first_name'].required = True 24 | self.fields['last_name'].required = True 25 | self.fields['email'].required = True 26 | class Meta: 27 | model = User 28 | fields = ('first_name', 'last_name', 'email') 29 | 30 | class UserProfileForm(forms.ModelForm): 31 | # override the default fields to force them to be required 32 | # (the django User model doesn't require them) 33 | def __init__(self, *args, **kwargs): 34 | super(UserProfileForm, self).__init__(*args, **kwargs) 35 | self.fields['encryption_key_timeout'].required = True 36 | class Meta: 37 | model = UserProfile 38 | fields = ('encryption_key_timeout',) 39 | -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.db import models 15 | from django.contrib.auth.models import User 16 | from django.db.models.signals import post_save 17 | from django.dispatch import receiver 18 | from uuid import uuid4 19 | from tastypie.models import create_api_key 20 | 21 | class UserProfile(models.Model): 22 | """ 23 | User profile 24 | 25 | """ 26 | user = models.ForeignKey(User, unique=True) 27 | encryption_key_timeout = models.IntegerField(default=3600, blank=True, 28 | null=True) 29 | is_pro = models.NullBooleanField(default=False, null=True, blank=True) 30 | pro_join_date = models.DateTimeField(null=True, blank=True) 31 | customer_id = models.CharField(max_length=64, null=True, blank=True) 32 | activation_code = models.CharField(max_length=64, null=True, blank=True) 33 | 34 | def __unicode__(self): 35 | return self.user.username 36 | 37 | # create user profile upon save 38 | @receiver(post_save, sender=User) 39 | def create_profile(sender, instance, created, **kwargs): 40 | if created: 41 | profile, created = UserProfile.objects.get_or_create(user=instance) 42 | 43 | models.signals.post_save.connect(create_api_key, sender=User) 44 | 45 | -------------------------------------------------------------------------------- /accounts/templates/accounts/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block main_content %} 5 |
6 |
7 |
8 | {% csrf_token %} 9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 | 42 |
43 |
44 |
45 | 53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /locksmith/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.shortcuts import redirect, render_to_response 15 | from django.template import RequestContext 16 | from django.http import HttpResponse 17 | from django.contrib.auth import authenticate, login as login_user 18 | from django.core.urlresolvers import reverse 19 | from locksmith.api_v1 import UserResource 20 | from social_auth.decorators import dsa_view 21 | import datetime 22 | 23 | def index(request): 24 | ctx = {} 25 | if request.user.is_authenticated(): 26 | return redirect(reverse('vault.views.index')) 27 | else: 28 | return render_to_response('index.html', ctx, 29 | context_instance=RequestContext(request)) 30 | 31 | def about(request): 32 | ctx = {} 33 | return render_to_response('about.html', ctx, 34 | context_instance=RequestContext(request)) 35 | 36 | @dsa_view() 37 | def register_by_access_token(request, backend, *args, **kwargs): 38 | access_token = request.GET.get('access_token') 39 | user = backend.do_auth(access_token) 40 | code = 200 41 | if user and user.is_active: 42 | login_user(request, user) 43 | ur = UserResource() 44 | user_data = ur.obj_get(request, username=user.username) 45 | bundle = ur.build_bundle(obj=user_data, request=request) 46 | data = ur.serialize(None, ur.full_dehydrate(bundle), 47 | 'application/json') 48 | else: 49 | data = json.dumps({'error': 'Access denied'}) 50 | code = 403 51 | return HttpResponse(data, status=code, 52 | content_type='application/json') 53 | 54 | -------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | 4 | {% block main_content %} 5 |
6 |
7 |

{% trans "What is Locksmith?" %}

8 |

{% blocktrans %}Locksmith is a password storage service. We all have many accounts across 9 | many services. We wanted a simple, secure way to store the account credentials 10 | with an easy way to retrieve them.{% endblocktrans %} 11 |

12 | 13 |

{% trans "How secure is it?" %}

14 |

{% blocktrans %}We take security at the highest priority. Locksmith takes a slightly 15 | different approach from that of a typical web service. Most services will 16 | encrypt or hash your password so in the event that it is compromised, it is not 17 | easily readable. However, they will typically use a single, unique key to 18 | encrypt all data. With Locksmith, we do the same type of encryption however, 19 | every single user also has a unique "key" that is used as an additional 20 | randomizer (salt) during encryption. What this means is that even if your data 21 | is compromised, which we take great measure to prevent, every single user's 22 | data is completely unique making it even more difficult to decrypt.{% endblocktrans %} 23 |

24 | 25 |

{% trans "How much does it cost?" %}

26 |

{% blocktrans %}Basic accounts are free. Currently, there is no difference between basic and pro accounts. 27 | In the future we will offer certain pro features but the current feature 28 | set will always be free.{% endblocktrans %}

29 | 30 |

{% trans "If you want to support development now, pro accounts are $12 per year." %}

31 | 32 |

{% trans "Any mobile apps?" %}

33 |

{% trans "In the works. Stay tuned!" %}

34 | 35 |

{% trans "Why Open Source?" %}

36 |

{% blocktrans %}First of all, we love open source. We try to commit as much as we 37 | can back to the open source community. Second, we believe that making 38 | Locksmith open source allows for community review and improvement. We feel 39 | this is especially important since our application contains your private data.{% endblocktrans %} 40 |

41 | 44 |
45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /utils/billing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2012 Evan Hazlett 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import stripe 16 | from django.conf import settings 17 | from django.utils.translation import ugettext as _ 18 | 19 | # set stripe api key 20 | stripe.api_key = getattr(settings, 'STRIPE_API_KEY') 21 | 22 | def create_customer(token=None, plan=None, email=None): 23 | customer = stripe.Customer.create( 24 | card=token, 25 | plan=plan, 26 | email=email 27 | ) 28 | return customer 29 | 30 | def charge(amount=None, currency='usd', card_number=None, 31 | card_exp_month=None, card_exp_year=None, card_cvc=None, card_name=None, 32 | description='Locksmith Payment'): 33 | """ 34 | Charges specified credit card for account 35 | 36 | :param amount: Amount in dollars 37 | :param currency: Currency (default: usd) 38 | :param card_number: Credit card number 39 | :param card_exp_month: Credit card expiration month (two digit integer) 40 | :param card_exp_year: Credit card expiration year (two or four digit integer) 41 | :param card_cvc: Credit card CVC 42 | :param card_name: Credit cardholder name 43 | :param description: Charge description (default: Locksmith Payment) 44 | 45 | """ 46 | # convert amount to cents 47 | amount = int(amount * 100) 48 | card_info = { 49 | 'number': card_number.replace('-', ''), 50 | 'exp_month': card_exp_month, 51 | 'exp_year': card_exp_year, 52 | 'cvc': card_cvc, 53 | 'name': card_name, 54 | } 55 | data = {} 56 | try: 57 | charge = stripe.Charge.create(amount=amount, currency=currency, card=card_info, 58 | description=description) 59 | if charge.paid: 60 | data['status'] = True 61 | data['message'] = _('Thanks!') 62 | else: 63 | data['status'] = False 64 | data['message'] = charge.failure_message 65 | data['created'] = charge.created 66 | except Exception, e: 67 | data['status'] = False 68 | data['message'] = e 69 | return data 70 | -------------------------------------------------------------------------------- /vault/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.db import models 15 | from django.contrib.auth.models import User 16 | from django.conf import settings 17 | from django.core.cache import cache 18 | from uuid import uuid4 19 | from utils.encryption import encrypt, get_user_encryption_key 20 | from locksmith.middleware import threadlocal 21 | 22 | def generate_uuid(): 23 | return str(uuid4()) 24 | 25 | class CredentialGroup(models.Model): 26 | uuid = models.CharField(max_length=36, blank=True, null=True, 27 | default=generate_uuid) 28 | name = models.CharField(max_length=64, blank=True, null=True) 29 | description = models.TextField(blank=True, null=True) 30 | owner = models.ForeignKey(User, blank=True, null=True, 31 | related_name='credential_group_owner') 32 | members = models.ManyToManyField(User, blank=True, null=True, 33 | related_name='credential_group_members') 34 | 35 | def __unicode__(self): 36 | return '{0}: {1}'.format(self.owner.username, self.name) 37 | 38 | def get_credentials(self): 39 | return Credential.objects.filter(groups__in=[self]) 40 | 41 | class Credential(models.Model): 42 | uuid = models.CharField(max_length=36, blank=True, null=True, 43 | default=generate_uuid) 44 | name = models.CharField(max_length=64, blank=True, null=True) 45 | description = models.TextField(blank=True, null=True) 46 | url = models.URLField(blank=True, null=True) 47 | username = models.CharField(max_length=96, blank=True, null=True) 48 | password = models.TextField(blank=True, null=True) 49 | groups = models.ManyToManyField(CredentialGroup, blank=True, null=True) 50 | 51 | def __unicode__(self): 52 | return '{0}: {1}'.format(','.join([x.name for x in self.groups.all()]), 53 | self.name) 54 | 55 | def save(self, *args, **kwargs): 56 | user = threadlocal.get_current_user() 57 | key = get_user_encryption_key(user.username) or kwargs.get('key') 58 | # if no key throw error 59 | if not key: 60 | raise StandardError("If calling save from outside of a request, " \ 61 | "you must specify 'key' as a kwarg") 62 | self.password = encrypt(self.password, key) 63 | super(Credential, self).save(*args, **kwargs) 64 | -------------------------------------------------------------------------------- /vault/templates/vault/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | {% load bootstrap_tags %} 4 | 5 | {% block main_content %} 6 | 31 | 32 | 35 |
36 | {% if credential_groups.all %} 37 | 49 | {% else %} 50 |

{% trans 'No groups' %}

51 | {% endif %} 52 |
53 | 77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /utils/encryption.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import hashlib 15 | from Crypto.Cipher import AES 16 | from django.utils.translation import ugettext as _ 17 | import base64 18 | from django.conf import settings 19 | from django.core.cache import cache 20 | from django.contrib.auth.models import User 21 | import string 22 | import random 23 | 24 | def generate_password(length=16): 25 | """ 26 | Generates a new random password 27 | 28 | :param length: Length of password 29 | 30 | """ 31 | return ''.join(random.sample(string.letters+string.digits, length)) 32 | 33 | def set_user_encryption_key(username=None, key=None, ttl=None): 34 | """ 35 | Sets the encryption key for the specified user 36 | 37 | """ 38 | if not ttl: 39 | u = User.objects.get(username=username) 40 | ttl = u.get_profile().encryption_key_timeout 41 | cache.set(settings.CACHE_ENCRYPTION_KEY.format(username), key, 42 | timeout=ttl) 43 | 44 | def get_user_encryption_key(username=None): 45 | """ 46 | Gets the encryption key for the specified user 47 | 48 | """ 49 | return cache.get(settings.CACHE_ENCRYPTION_KEY.format(username)) 50 | 51 | def clear_user_encryption_key(username=None): 52 | """ 53 | Clears the encryption key for the specified user 54 | 55 | """ 56 | cache.delete(settings.CACHE_ENCRYPTION_KEY.format(username)) 57 | 58 | def hash_text(text): 59 | """ 60 | Hashes text with app key 61 | 62 | :param text: Text to encrypt 63 | 64 | """ 65 | h = hashlib.sha256() 66 | h.update(getattr(settings, 'SECRET_KEY')) 67 | h.update(text) 68 | return h.hexdigest() 69 | 70 | def _get_padded_key(key=None): 71 | if len(key) < 16: 72 | pad = 16 - len(key) 73 | k = key + ('^'*pad) 74 | else: 75 | k = key[:16] 76 | return k 77 | 78 | def encrypt(data=None, key=None): 79 | """ 80 | Encrypts data 81 | 82 | :param data: Data to encrypt 83 | :param key: Encryption key (salt) 84 | 85 | """ 86 | k = _get_padded_key(key) 87 | e = AES.new(k, AES.MODE_CFB, k[::-1]) 88 | enc = e.encrypt(data) 89 | return base64.b64encode(enc) 90 | 91 | def decrypt(data=None, key=None): 92 | """ 93 | Decrypts data 94 | 95 | :param data: Encrypted data to decrypt 96 | :param key: Encryption key (salt) 97 | 98 | """ 99 | k = _get_padded_key(key) 100 | e = AES.new(k, AES.MODE_CFB, k[::-1]) 101 | dec = e.decrypt(base64.b64decode(data)) 102 | try: 103 | unicode(dec) 104 | except: 105 | dec = '' 106 | return dec 107 | -------------------------------------------------------------------------------- /vault/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.shortcuts import redirect, render_to_response 15 | from django.template import RequestContext 16 | from django.contrib.auth.decorators import login_required 17 | from django.views.decorators.http import require_http_methods 18 | from django.http import HttpResponse, Http404 19 | from django.utils.translation import ugettext as _ 20 | from django.db.models import Q 21 | from django.core.urlresolvers import reverse 22 | from django.core.cache import cache 23 | from django.conf import settings 24 | from django.contrib import messages 25 | from vault.models import CredentialGroup, Credential 26 | from vault.forms import CredentialGroupForm 27 | from utils.encryption import (set_user_encryption_key, clear_user_encryption_key, 28 | get_user_encryption_key, generate_password) 29 | try: 30 | import simplejson as json 31 | except ImportError: 32 | import json 33 | 34 | @login_required 35 | def index(request): 36 | ctx = {} 37 | try: 38 | groups = CredentialGroup.objects.filter(Q(owner=request.user) | \ 39 | Q(members__in=[request.user])).order_by('name') 40 | ctx['credential_groups'] = groups 41 | except CredentialGroup.DoesNotExist: 42 | raise Http404() 43 | return render_to_response('vault/index.html', ctx, 44 | context_instance=RequestContext(request)) 45 | 46 | @login_required 47 | def group(request, uuid=None): 48 | ctx = {} 49 | try: 50 | group = CredentialGroup.objects.get(Q(owner=request.user) | \ 51 | Q(members__in=[request.user]), uuid=uuid) 52 | ctx['group'] = group 53 | except CredentialGroup.DoesNotExist: 54 | raise Http404() 55 | return render_to_response('vault/group.html', ctx, 56 | context_instance=RequestContext(request)) 57 | 58 | @login_required 59 | @require_http_methods(["POST"]) 60 | def set_key(request): 61 | nxt = request.GET.get('next', reverse('index')) 62 | key = request.POST.get('key') 63 | u = request.user 64 | set_user_encryption_key(u.username, key) 65 | return redirect(nxt) 66 | 67 | @login_required 68 | def lock_session(request): 69 | nxt = request.GET.get('next', reverse('index')) 70 | clear_user_encryption_key(request.user.username) 71 | return redirect(nxt) 72 | 73 | @login_required 74 | def random_password(request): 75 | return HttpResponse(generate_password()) 76 | 77 | @login_required 78 | def check_session(request): 79 | key = get_user_encryption_key(request.user.username) 80 | if key: 81 | key = True 82 | else: 83 | key = False 84 | data = { 85 | 'status': key, 86 | } 87 | return HttpResponse(json.dumps(data), content_type='application/json') 88 | -------------------------------------------------------------------------------- /accounts/templates/accounts/activate.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block main_content %} 5 |
6 |

{% trans '$12 will be charged to your card anually. Thank you very much!' %}

7 |
8 |
9 | {% csrf_token %} 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 |
32 | 47 | 63 |
64 |
65 |
66 | {% trans 'Signup!' %} 67 | 🔒 {% trans 'We use' %} Stripe.js {% trans 'and never store your credit card information.' %} 68 |
69 |
70 | 71 | 72 | 73 | 97 | {% endblock %} 98 | -------------------------------------------------------------------------------- /accounts/migrations/0002_auto__add_field_userprofile_is_pro.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'UserProfile.is_pro' 12 | db.add_column('accounts_userprofile', 'is_pro', 13 | self.gf('django.db.models.fields.NullBooleanField')(default=False, null=True, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'UserProfile.is_pro' 19 | db.delete_column('accounts_userprofile', 'is_pro') 20 | 21 | 22 | models = { 23 | 'accounts.userprofile': { 24 | 'Meta': {'object_name': 'UserProfile'}, 25 | 'encryption_key_timeout': ('django.db.models.fields.IntegerField', [], {'default': '3600', 'null': 'True', 'blank': 'True'}), 26 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'is_pro': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), 28 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 29 | }, 30 | 'auth.group': { 31 | 'Meta': {'object_name': 'Group'}, 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 34 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 35 | }, 36 | 'auth.permission': { 37 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 38 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 39 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 40 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 41 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 42 | }, 43 | 'auth.user': { 44 | 'Meta': {'object_name': 'User'}, 45 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 47 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 48 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 50 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 51 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 52 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 53 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 54 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 55 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 56 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 57 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 58 | }, 59 | 'contenttypes.contenttype': { 60 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 61 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 65 | } 66 | } 67 | 68 | complete_apps = ['accounts'] -------------------------------------------------------------------------------- /static/js/app.js: -------------------------------------------------------------------------------- 1 | function flash(text, status){ 2 | var msg = $("
"); 3 | msg.addClass('alert-'+status); 4 | msg.append("x"); 5 | msg.append('

'+text+'

'); 6 | $("#messages").append(msg); 7 | $("#messages").removeClass('hide'); 8 | $(".alert").alert(); 9 | $(".alert").delay(5000).fadeOut(); 10 | } 11 | 12 | function redirect(url) { 13 | window.location.href = url; 14 | return false; 15 | } 16 | 17 | jQuery.fn.serializeObject = function() { 18 | var arrayData, objectData; 19 | arrayData = this.serializeArray(); 20 | objectData = {}; 21 | $.each(arrayData, function() { 22 | var value; 23 | if (this.value != null) { 24 | value = this.value; 25 | } else { 26 | value = ''; 27 | } 28 | if (objectData[this.name] != null) { 29 | if (!objectData[this.name].push) { 30 | objectData[this.name] = [objectData[this.name]]; 31 | } 32 | objectData[this.name].push(value); 33 | } else { 34 | objectData[this.name] = value; 35 | } 36 | }); 37 | return objectData; 38 | }; 39 | 40 | // TODO: redesign this 41 | // api 42 | var API_URL = '/api/v1/'; 43 | function createCredentialGroup(options, callback) { 44 | var opt = options || {}; 45 | var data = { 46 | name: opt.name 47 | } 48 | var cb = callback || function(){}; 49 | $.ajax({ 50 | url: API_URL + 'credentialgroups/', 51 | data: JSON.stringify(data), 52 | type: "POST", 53 | dataType: "application/json", 54 | contentType: "application/json", 55 | complete: function(xhr) { 56 | cb(xhr); 57 | } 58 | }); 59 | } 60 | function editCredentialGroup(options, callback) { 61 | var data = options || {}; 62 | var cb = callback || function(){}; 63 | $.ajax({ 64 | url: API_URL + 'credentialgroups/' + data.uuid, 65 | data: JSON.stringify(data), 66 | type: "PATCH", 67 | dataType: "application/json", 68 | contentType: "application/json", 69 | complete: function(xhr) { 70 | cb(xhr); 71 | } 72 | }); 73 | 74 | } 75 | function deleteCredentialGroup(uuid, callback) { 76 | var cb = callback || function(){}; 77 | $.ajax({ 78 | url: API_URL + 'credentialgroups/' + uuid + '/', 79 | type: "DELETE", 80 | complete: function(xhr) { 81 | cb(xhr); 82 | } 83 | }); 84 | } 85 | function createCredential(options, callback) { 86 | var opt = options || {}; 87 | var data = { 88 | name: opt.name, 89 | description: opt.description, 90 | url: opt.url, 91 | username: opt.username, 92 | password: opt.password, 93 | groups: [API_URL + "credentialgroups/" + opt.groupUUID + "/"] 94 | } 95 | var cb = callback || function(){}; 96 | $.ajax({ 97 | url: API_URL + 'credentials/', 98 | data: JSON.stringify(data), 99 | type: "POST", 100 | dataType: "application/json", 101 | contentType: "application/json", 102 | complete: function(xhr) { 103 | cb(xhr); 104 | } 105 | }); 106 | } 107 | function getCredential(uuid, callback) { 108 | var cb = callback || function(){}; 109 | $.ajax({ 110 | url: API_URL + 'credentials/' + uuid + '/', 111 | type: "GET", 112 | complete: function(xhr) { 113 | var data = JSON.parse(xhr.responseText); 114 | cb(data, xhr); 115 | } 116 | }); 117 | } 118 | function updateCredential(options, callback) { 119 | var opt = options || {} 120 | var cb = callback || function(){}; 121 | var data = { 122 | name: opt.name, 123 | description: opt.description, 124 | url: opt.url, 125 | username: opt.username, 126 | password: opt.password 127 | } 128 | $.ajax({ 129 | url: API_URL + 'credentials/' + opt.uuid, 130 | data: JSON.stringify(data), 131 | type: "PATCH", 132 | dataType: "application/json", 133 | contentType: "application/json", 134 | complete: function(xhr) { 135 | cb(xhr); 136 | } 137 | }); 138 | } 139 | function deleteCredential(uuid, callback) { 140 | var cb = callback || function(){}; 141 | $.ajax({ 142 | url: API_URL + 'credentials/' + uuid + '/', 143 | type: "DELETE", 144 | complete: function(xhr) { 145 | cb(xhr); 146 | } 147 | }); 148 | } 149 | -------------------------------------------------------------------------------- /accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'UserProfile' 12 | db.create_table('accounts_userprofile', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True)), 15 | ('encryption_key_timeout', self.gf('django.db.models.fields.IntegerField')(default=3600, null=True, blank=True)), 16 | )) 17 | db.send_create_signal('accounts', ['UserProfile']) 18 | 19 | 20 | def backwards(self, orm): 21 | # Deleting model 'UserProfile' 22 | db.delete_table('accounts_userprofile') 23 | 24 | 25 | models = { 26 | 'accounts.userprofile': { 27 | 'Meta': {'object_name': 'UserProfile'}, 28 | 'encryption_key_timeout': ('django.db.models.fields.IntegerField', [], {'default': '3600', 'null': 'True', 'blank': 'True'}), 29 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 31 | }, 32 | 'auth.group': { 33 | 'Meta': {'object_name': 'Group'}, 34 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 36 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 37 | }, 38 | 'auth.permission': { 39 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 40 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 41 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 42 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 43 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 44 | }, 45 | 'auth.user': { 46 | 'Meta': {'object_name': 'User'}, 47 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 48 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 49 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 50 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 51 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 53 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 55 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 56 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 57 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 58 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 59 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 60 | }, 61 | 'contenttypes.contenttype': { 62 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 63 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 66 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 67 | } 68 | } 69 | 70 | complete_apps = ['accounts'] -------------------------------------------------------------------------------- /accounts/migrations/0003_auto__add_field_userprofile_pro_join_date.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'UserProfile.pro_join_date' 12 | db.add_column('accounts_userprofile', 'pro_join_date', 13 | self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'UserProfile.pro_join_date' 19 | db.delete_column('accounts_userprofile', 'pro_join_date') 20 | 21 | 22 | models = { 23 | 'accounts.userprofile': { 24 | 'Meta': {'object_name': 'UserProfile'}, 25 | 'encryption_key_timeout': ('django.db.models.fields.IntegerField', [], {'default': '3600', 'null': 'True', 'blank': 'True'}), 26 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'is_pro': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), 28 | 'pro_join_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 29 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 30 | }, 31 | 'auth.group': { 32 | 'Meta': {'object_name': 'Group'}, 33 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 35 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 36 | }, 37 | 'auth.permission': { 38 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 39 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 40 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 43 | }, 44 | 'auth.user': { 45 | 'Meta': {'object_name': 'User'}, 46 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 47 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 48 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 49 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 50 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 51 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 52 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 53 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 55 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 56 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 57 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 58 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 59 | }, 60 | 'contenttypes.contenttype': { 61 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 62 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 63 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 64 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 65 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 66 | } 67 | } 68 | 69 | complete_apps = ['accounts'] -------------------------------------------------------------------------------- /accounts/migrations/0004_auto__add_field_userprofile_customer_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'UserProfile.customer_id' 12 | db.add_column('accounts_userprofile', 'customer_id', 13 | self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'UserProfile.customer_id' 19 | db.delete_column('accounts_userprofile', 'customer_id') 20 | 21 | 22 | models = { 23 | 'accounts.userprofile': { 24 | 'Meta': {'object_name': 'UserProfile'}, 25 | 'customer_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 26 | 'encryption_key_timeout': ('django.db.models.fields.IntegerField', [], {'default': '3600', 'null': 'True', 'blank': 'True'}), 27 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 28 | 'is_pro': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), 29 | 'pro_join_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 30 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 31 | }, 32 | 'auth.group': { 33 | 'Meta': {'object_name': 'Group'}, 34 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 36 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 37 | }, 38 | 'auth.permission': { 39 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 40 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 41 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 42 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 43 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 44 | }, 45 | 'auth.user': { 46 | 'Meta': {'object_name': 'User'}, 47 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 48 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 49 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 50 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 51 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 53 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 55 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 56 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 57 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 58 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 59 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 60 | }, 61 | 'contenttypes.contenttype': { 62 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 63 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 66 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 67 | } 68 | } 69 | 70 | complete_apps = ['accounts'] -------------------------------------------------------------------------------- /accounts/migrations/0005_auto__chg_field_userprofile_customer_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'UserProfile.customer_id' 13 | db.alter_column('accounts_userprofile', 'customer_id', self.gf('django.db.models.fields.CharField')(max_length=64, null=True)) 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'UserProfile.customer_id' 18 | db.alter_column('accounts_userprofile', 'customer_id', self.gf('django.db.models.fields.PositiveIntegerField')(null=True)) 19 | 20 | models = { 21 | 'accounts.userprofile': { 22 | 'Meta': {'object_name': 'UserProfile'}, 23 | 'customer_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 24 | 'encryption_key_timeout': ('django.db.models.fields.IntegerField', [], {'default': '3600', 'null': 'True', 'blank': 'True'}), 25 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 26 | 'is_pro': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), 27 | 'pro_join_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 28 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 29 | }, 30 | 'auth.group': { 31 | 'Meta': {'object_name': 'Group'}, 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 34 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 35 | }, 36 | 'auth.permission': { 37 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 38 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 39 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 40 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 41 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 42 | }, 43 | 'auth.user': { 44 | 'Meta': {'object_name': 'User'}, 45 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 47 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 48 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 50 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 51 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 52 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 53 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 54 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 55 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 56 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 57 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 58 | }, 59 | 'contenttypes.contenttype': { 60 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 61 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 65 | } 66 | } 67 | 68 | complete_apps = ['accounts'] -------------------------------------------------------------------------------- /accounts/migrations/0006_auto__add_field_userprofile_activation_code.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'UserProfile.activation_code' 12 | db.add_column('accounts_userprofile', 'activation_code', 13 | self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'UserProfile.activation_code' 19 | db.delete_column('accounts_userprofile', 'activation_code') 20 | 21 | 22 | models = { 23 | 'accounts.userprofile': { 24 | 'Meta': {'object_name': 'UserProfile'}, 25 | 'activation_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 26 | 'customer_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 27 | 'encryption_key_timeout': ('django.db.models.fields.IntegerField', [], {'default': '3600', 'null': 'True', 'blank': 'True'}), 28 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'is_pro': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), 30 | 'pro_join_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 31 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 32 | }, 33 | 'auth.group': { 34 | 'Meta': {'object_name': 'Group'}, 35 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 37 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 38 | }, 39 | 'auth.permission': { 40 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 41 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 42 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 43 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 45 | }, 46 | 'auth.user': { 47 | 'Meta': {'object_name': 'User'}, 48 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 49 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 50 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 51 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 52 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 53 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 54 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 55 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 56 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 57 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 58 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 59 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 60 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 61 | }, 62 | 'contenttypes.contenttype': { 63 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 64 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 65 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 67 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 68 | } 69 | } 70 | 71 | complete_apps = ['accounts'] -------------------------------------------------------------------------------- /vault/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Credential' 12 | db.create_table('vault_credential', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)), 15 | ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), 16 | ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), 17 | ('url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)), 18 | ('key', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), 19 | )) 20 | db.send_create_signal('vault', ['Credential']) 21 | 22 | 23 | def backwards(self, orm): 24 | # Deleting model 'Credential' 25 | db.delete_table('vault_credential') 26 | 27 | 28 | models = { 29 | 'auth.group': { 30 | 'Meta': {'object_name': 'Group'}, 31 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 32 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 33 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 34 | }, 35 | 'auth.permission': { 36 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 37 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 38 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 39 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 40 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 41 | }, 42 | 'auth.user': { 43 | 'Meta': {'object_name': 'User'}, 44 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 45 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 46 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 47 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 48 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 49 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 50 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 51 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 52 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 53 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 54 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 55 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 56 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 57 | }, 58 | 'contenttypes.contenttype': { 59 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 60 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 62 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 63 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 64 | }, 65 | 'vault.credential': { 66 | 'Meta': {'object_name': 'Credential'}, 67 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 68 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 69 | 'key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 70 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 71 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), 72 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) 73 | } 74 | } 75 | 76 | complete_apps = ['vault'] -------------------------------------------------------------------------------- /vault/migrations/0004_auto__add_field_credentialgroup_name__add_field_credentialgroup_descri.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'CredentialGroup.name' 12 | db.add_column('vault_credentialgroup', 'name', 13 | self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True), 14 | keep_default=False) 15 | 16 | # Adding field 'CredentialGroup.description' 17 | db.add_column('vault_credentialgroup', 'description', 18 | self.gf('django.db.models.fields.TextField')(null=True, blank=True), 19 | keep_default=False) 20 | 21 | 22 | def backwards(self, orm): 23 | # Deleting field 'CredentialGroup.name' 24 | db.delete_column('vault_credentialgroup', 'name') 25 | 26 | # Deleting field 'CredentialGroup.description' 27 | db.delete_column('vault_credentialgroup', 'description') 28 | 29 | 30 | models = { 31 | 'auth.group': { 32 | 'Meta': {'object_name': 'Group'}, 33 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 35 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 36 | }, 37 | 'auth.permission': { 38 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 39 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 40 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 43 | }, 44 | 'auth.user': { 45 | 'Meta': {'object_name': 'User'}, 46 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 47 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 48 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 49 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 50 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 51 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 52 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 53 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 55 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 56 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 57 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 58 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 59 | }, 60 | 'contenttypes.contenttype': { 61 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 62 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 63 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 64 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 65 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 66 | }, 67 | 'vault.credential': { 68 | 'Meta': {'object_name': 'Credential'}, 69 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 70 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['vault.CredentialGroup']", 'null': 'True', 'blank': 'True'}), 71 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 73 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), 74 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) 75 | }, 76 | 'vault.credentialgroup': { 77 | 'Meta': {'object_name': 'CredentialGroup'}, 78 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 79 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 80 | 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'credential_group_members'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), 81 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 82 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'credential_group_owner'", 'null': 'True', 'to': "orm['auth.User']"}) 83 | } 84 | } 85 | 86 | complete_apps = ['vault'] -------------------------------------------------------------------------------- /vault/migrations/0003_auto__del_field_credential_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'Credential.group' 12 | db.delete_column('vault_credential', 'group_id') 13 | 14 | # Adding M2M table for field groups on 'Credential' 15 | db.create_table('vault_credential_groups', ( 16 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 17 | ('credential', models.ForeignKey(orm['vault.credential'], null=False)), 18 | ('credentialgroup', models.ForeignKey(orm['vault.credentialgroup'], null=False)) 19 | )) 20 | db.create_unique('vault_credential_groups', ['credential_id', 'credentialgroup_id']) 21 | 22 | 23 | def backwards(self, orm): 24 | # Adding field 'Credential.group' 25 | db.add_column('vault_credential', 'group', 26 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['vault.CredentialGroup'], null=True, blank=True), 27 | keep_default=False) 28 | 29 | # Removing M2M table for field groups on 'Credential' 30 | db.delete_table('vault_credential_groups') 31 | 32 | 33 | models = { 34 | 'auth.group': { 35 | 'Meta': {'object_name': 'Group'}, 36 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 38 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 39 | }, 40 | 'auth.permission': { 41 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 42 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 43 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 44 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 45 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 46 | }, 47 | 'auth.user': { 48 | 'Meta': {'object_name': 'User'}, 49 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 50 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 51 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 52 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 53 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 54 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 55 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 56 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 57 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 58 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 59 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 60 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 61 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 62 | }, 63 | 'contenttypes.contenttype': { 64 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 65 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 66 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 67 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 68 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 69 | }, 70 | 'vault.credential': { 71 | 'Meta': {'object_name': 'Credential'}, 72 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 73 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['vault.CredentialGroup']", 'null': 'True', 'blank': 'True'}), 74 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 75 | 'key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 76 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 77 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) 78 | }, 79 | 'vault.credentialgroup': { 80 | 'Meta': {'object_name': 'CredentialGroup'}, 81 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 82 | 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'credential_group_members'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), 83 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'credential_group_owner'", 'null': 'True', 'to': "orm['auth.User']"}) 84 | } 85 | } 86 | 87 | complete_apps = ['vault'] -------------------------------------------------------------------------------- /vault/migrations/0005_auto__add_field_credential_uuid__add_field_credentialgroup_uuid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Credential.uuid' 12 | db.add_column('vault_credential', 'uuid', 13 | self.gf('django.db.models.fields.CharField')(default='6f2fae29-03f9-4daa-8e71-8bf965d43008', max_length=36, null=True, blank=True), 14 | keep_default=False) 15 | 16 | # Adding field 'CredentialGroup.uuid' 17 | db.add_column('vault_credentialgroup', 'uuid', 18 | self.gf('django.db.models.fields.CharField')(default='5045c41b-f9cb-4265-a4ff-a00fac0d5ae4', max_length=36, null=True, blank=True), 19 | keep_default=False) 20 | 21 | 22 | def backwards(self, orm): 23 | # Deleting field 'Credential.uuid' 24 | db.delete_column('vault_credential', 'uuid') 25 | 26 | # Deleting field 'CredentialGroup.uuid' 27 | db.delete_column('vault_credentialgroup', 'uuid') 28 | 29 | 30 | models = { 31 | 'auth.group': { 32 | 'Meta': {'object_name': 'Group'}, 33 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 35 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 36 | }, 37 | 'auth.permission': { 38 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 39 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 40 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 43 | }, 44 | 'auth.user': { 45 | 'Meta': {'object_name': 'User'}, 46 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 47 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 48 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 49 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 50 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 51 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 52 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 53 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 55 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 56 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 57 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 58 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 59 | }, 60 | 'contenttypes.contenttype': { 61 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 62 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 63 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 64 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 65 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 66 | }, 67 | 'vault.credential': { 68 | 'Meta': {'object_name': 'Credential'}, 69 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 70 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['vault.CredentialGroup']", 'null': 'True', 'blank': 'True'}), 71 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 73 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 74 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), 75 | 'uuid': ('django.db.models.fields.CharField', [], {'default': "'e6a4765f-3704-4f55-ad0e-c6d6f3b9823b'", 'max_length': '36', 'null': 'True', 'blank': 'True'}) 76 | }, 77 | 'vault.credentialgroup': { 78 | 'Meta': {'object_name': 'CredentialGroup'}, 79 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 80 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 81 | 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'credential_group_members'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), 82 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 83 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'credential_group_owner'", 'null': 'True', 'to': "orm['auth.User']"}), 84 | 'uuid': ('django.db.models.fields.CharField', [], {'default': "'c7581278-b58b-479b-bb46-ab2389313cf0'", 'max_length': '36', 'null': 'True', 'blank': 'True'}) 85 | } 86 | } 87 | 88 | complete_apps = ['vault'] -------------------------------------------------------------------------------- /vault/migrations/0006_auto__del_field_credential_key__add_field_credential_username__add_fie.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'Credential.key' 12 | db.delete_column('vault_credential', 'key') 13 | 14 | # Adding field 'Credential.username' 15 | db.add_column('vault_credential', 'username', 16 | self.gf('django.db.models.fields.CharField')(max_length=96, null=True, blank=True), 17 | keep_default=False) 18 | 19 | # Adding field 'Credential.password' 20 | db.add_column('vault_credential', 'password', 21 | self.gf('django.db.models.fields.TextField')(null=True, blank=True), 22 | keep_default=False) 23 | 24 | 25 | def backwards(self, orm): 26 | # Adding field 'Credential.key' 27 | db.add_column('vault_credential', 'key', 28 | self.gf('django.db.models.fields.TextField')(null=True, blank=True), 29 | keep_default=False) 30 | 31 | # Deleting field 'Credential.username' 32 | db.delete_column('vault_credential', 'username') 33 | 34 | # Deleting field 'Credential.password' 35 | db.delete_column('vault_credential', 'password') 36 | 37 | 38 | models = { 39 | 'auth.group': { 40 | 'Meta': {'object_name': 'Group'}, 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 43 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 44 | }, 45 | 'auth.permission': { 46 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 47 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 48 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 49 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 50 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 51 | }, 52 | 'auth.user': { 53 | 'Meta': {'object_name': 'User'}, 54 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 55 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 56 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 57 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 58 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 60 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 61 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 62 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 63 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 64 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 65 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 66 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 67 | }, 68 | 'contenttypes.contenttype': { 69 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 70 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 71 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 73 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 74 | }, 75 | 'vault.credential': { 76 | 'Meta': {'object_name': 'Credential'}, 77 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 78 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['vault.CredentialGroup']", 'null': 'True', 'blank': 'True'}), 79 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 80 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 81 | 'password': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 82 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), 83 | 'username': ('django.db.models.fields.CharField', [], {'max_length': '96', 'null': 'True', 'blank': 'True'}), 84 | 'uuid': ('django.db.models.fields.CharField', [], {'default': "'fcfc33de-6751-43c2-8b2b-6720e62959d7'", 'max_length': '36', 'null': 'True', 'blank': 'True'}) 85 | }, 86 | 'vault.credentialgroup': { 87 | 'Meta': {'object_name': 'CredentialGroup'}, 88 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 89 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 90 | 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'credential_group_members'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), 91 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 92 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'credential_group_owner'", 'null': 'True', 'to': "orm['auth.User']"}), 93 | 'uuid': ('django.db.models.fields.CharField', [], {'default': "'90b3abf8-00fe-46c3-8a37-8c0e0331b4f1'", 'max_length': '36', 'null': 'True', 'blank': 'True'}) 94 | } 95 | } 96 | 97 | complete_apps = ['vault'] -------------------------------------------------------------------------------- /vault/migrations/0002_auto__add_credentialgroup__del_field_credential_owner__add_field_crede.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'CredentialGroup' 12 | db.create_table('vault_credentialgroup', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('owner', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='credential_group_owner', null=True, to=orm['auth.User'])), 15 | )) 16 | db.send_create_signal('vault', ['CredentialGroup']) 17 | 18 | # Adding M2M table for field members on 'CredentialGroup' 19 | db.create_table('vault_credentialgroup_members', ( 20 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 21 | ('credentialgroup', models.ForeignKey(orm['vault.credentialgroup'], null=False)), 22 | ('user', models.ForeignKey(orm['auth.user'], null=False)) 23 | )) 24 | db.create_unique('vault_credentialgroup_members', ['credentialgroup_id', 'user_id']) 25 | 26 | # Deleting field 'Credential.owner' 27 | db.delete_column('vault_credential', 'owner_id') 28 | 29 | # Adding field 'Credential.group' 30 | db.add_column('vault_credential', 'group', 31 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['vault.CredentialGroup'], null=True, blank=True), 32 | keep_default=False) 33 | 34 | 35 | def backwards(self, orm): 36 | # Deleting model 'CredentialGroup' 37 | db.delete_table('vault_credentialgroup') 38 | 39 | # Removing M2M table for field members on 'CredentialGroup' 40 | db.delete_table('vault_credentialgroup_members') 41 | 42 | # Adding field 'Credential.owner' 43 | db.add_column('vault_credential', 'owner', 44 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True), 45 | keep_default=False) 46 | 47 | # Deleting field 'Credential.group' 48 | db.delete_column('vault_credential', 'group_id') 49 | 50 | 51 | models = { 52 | 'auth.group': { 53 | 'Meta': {'object_name': 'Group'}, 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 56 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 57 | }, 58 | 'auth.permission': { 59 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 60 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 61 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 64 | }, 65 | 'auth.user': { 66 | 'Meta': {'object_name': 'User'}, 67 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 68 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 69 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 70 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 71 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 73 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 74 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 75 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 76 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 77 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 78 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 79 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 80 | }, 81 | 'contenttypes.contenttype': { 82 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 83 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 84 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 85 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 86 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 87 | }, 88 | 'vault.credential': { 89 | 'Meta': {'object_name': 'Credential'}, 90 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 91 | 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['vault.CredentialGroup']", 'null': 'True', 'blank': 'True'}), 92 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 93 | 'key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 94 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 95 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) 96 | }, 97 | 'vault.credentialgroup': { 98 | 'Meta': {'object_name': 'CredentialGroup'}, 99 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 100 | 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'credential_group_members'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), 101 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'credential_group_owner'", 'null': 'True', 'to': "orm['auth.User']"}) 102 | } 103 | } 104 | 105 | complete_apps = ['vault'] -------------------------------------------------------------------------------- /accounts/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from django.shortcuts import render_to_response, redirect 15 | from django.core.urlresolvers import reverse 16 | from django.template import RequestContext 17 | from django.http import HttpResponse 18 | from django.views.decorators.http import require_http_methods 19 | from django.contrib.auth.decorators import login_required 20 | from django.contrib.auth import (authenticate, login as login_user, 21 | logout as logout_user) 22 | from django.contrib.auth.models import User 23 | from django.contrib import messages 24 | from django.conf import settings 25 | from django.core.mail import send_mail 26 | from django.views.decorators.csrf import csrf_exempt 27 | from django.utils.translation import ugettext as _ 28 | from accounts.forms import AccountForm, UserProfileForm 29 | from accounts.models import UserProfile 30 | from datetime import datetime 31 | from utils import billing 32 | import random 33 | import string 34 | try: 35 | import simplejson as json 36 | except ImportError: 37 | import json 38 | 39 | @require_http_methods(["GET", "POST"]) 40 | def login(request): 41 | if request.method == 'POST': 42 | username = request.POST.get('username') 43 | password = request.POST.get('password') 44 | user = authenticate(username=username, password=password) 45 | if user is not None: 46 | if user.is_active: 47 | login_user(request, user) 48 | return redirect(reverse('index')) 49 | else: 50 | messages.error(request, _('Your account is disabled. Make sure you have activated your account.')) 51 | else: 52 | messages.error(request, _('Invalid username/password')) 53 | return render_to_response('accounts/login.html', 54 | context_instance=RequestContext(request)) 55 | 56 | def logout(request): 57 | logout_user(request) 58 | return redirect(reverse('index')) 59 | 60 | @login_required 61 | def details(request): 62 | ctx = {} 63 | form = AccountForm(instance=request.user) 64 | pform = UserProfileForm(instance=request.user.get_profile()) 65 | if request.method == 'POST': 66 | form = AccountForm(request.POST, instance=request.user) 67 | pform = UserProfileForm(request.POST, 68 | instance=request.user.get_profile()) 69 | if form.is_valid() and pform.is_valid(): 70 | form.save() 71 | pform.save() 72 | messages.info(request, _('Account updated.')) 73 | ctx['form'] = form 74 | ctx['pform'] = pform 75 | return render_to_response('accounts/details.html', ctx, 76 | context_instance=RequestContext(request)) 77 | 78 | def confirm(request, code=None): 79 | up = UserProfile.objects.get(activation_code=code) 80 | user = up.user 81 | user.is_active = True 82 | user.save() 83 | messages.success(request, _('Thanks! You may now login.')) 84 | return redirect(reverse('accounts.login')) 85 | 86 | def signup(request): 87 | ctx = {} 88 | if not settings.SIGNUP_ENABLED: 89 | messages.warning(request, _('Signup is not enabled at this time.')) 90 | return redirect(reverse('index')) 91 | if request.method == 'POST': 92 | username = request.POST.get('username') 93 | password = request.POST.get('password') 94 | first_name = request.POST.get('first_name') 95 | last_name = request.POST.get('last_name') 96 | username = request.POST.get('username') 97 | email = request.POST.get('email') 98 | user = User(first_name=first_name, last_name=last_name, 99 | email=email) 100 | user.username = username 101 | user.set_password(password) 102 | user.is_active = False 103 | user.save() 104 | # generate code 105 | code = ''.join(random.sample(string.letters+string.digits, 16)) 106 | up = user.get_profile() 107 | up.activation_code = code 108 | up.save() 109 | # send welcome 110 | tmpl = """Thanks for signing up! 111 | 112 | Please activate your account by clicking the following link: 113 | 114 | http://{0}{1} 115 | 116 | Please feel free to request features, submit bug reports, check the wiki, etc. 117 | at https://github.com/ehazlett/locksmith/wiki 118 | 119 | If you have any questions please feel free to contact us at support@vitasso.com. 120 | 121 | Thanks! 122 | Locksmith Team 123 | """.format(request.get_host(), reverse('accounts.confirm', args=[code])) 124 | send_mail(_('Welcome to Locksmith!'), tmpl, settings.ADMIN_EMAIL, 125 | [user.email], fail_silently=True) 126 | messages.success(request, _('Thanks! Please check your email to activate.')) 127 | return redirect(reverse('index')) 128 | return render_to_response('accounts/signup.html', ctx, 129 | context_instance=RequestContext(request)) 130 | 131 | @login_required 132 | def activate(request): 133 | ctx = {} 134 | if request.method == 'POST': 135 | token = request.POST.get('token') 136 | try: 137 | customer = billing.create_customer(token, settings.ACCOUNT_PLAN, 138 | request.user.email) 139 | up = request.user.get_profile() 140 | up.customer_id = customer.id 141 | up.save() 142 | messages.success(request, _('Thanks for supporting! Please let us know if you have any questions.')) 143 | return redirect(reverse('index')) 144 | except Exception, e: 145 | messages.error(request, '{0}:{1}'.format( 146 | _('Error processing payment'), e)) 147 | return render_to_response('accounts/activate.html', ctx, 148 | context_instance=RequestContext(request)) 149 | 150 | @csrf_exempt 151 | def hook(request): 152 | event = json.loads(request.body) 153 | print(event) 154 | event_type = event.get('type') 155 | # subscription payment success 156 | if event_type == 'invoice.payment_succeeded': 157 | customer = event.get('data', {}).get('object', {}).get('customer') 158 | up = UserProfile.objects.get(customer_id=customer) 159 | if settings.DEBUG or event.get('livemode'): 160 | up.is_pro = True 161 | up.pro_join_date = datetime.now() 162 | up.save() 163 | # subscription ended 164 | if event_type == 'customer.subscription.deleted' or \ 165 | event_type == 'charge.refunded' or event_type == 'charge.failed' or \ 166 | event_type == 'customer.subscription.deleted' or \ 167 | event_type == 'invoice.payment_failed': 168 | customer = event.get('data', {}).get('object', {}).get('customer') 169 | up = UserProfile.objects.get(customer_id=customer) 170 | if settings.DEBUG or event.get('livemode'): 171 | up.is_pro = False 172 | up.save() 173 | return HttpResponse(status=200) 174 | -------------------------------------------------------------------------------- /static/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | .content { 5 | min-height: 300px; 6 | } 7 | .control-label { 8 | color: #777777; 9 | } 10 | a:hover { 11 | text-decoration: none; 12 | } 13 | .alert { 14 | position: fixed; 15 | overflow: visible; 16 | top: 45px; 17 | left: 10px; 18 | right: 10px; 19 | z-index: 999; 20 | text-align: center; 21 | } 22 | .info { 23 | color: #8a8a8a; 24 | } 25 | .brand i { 26 | position: relative; 27 | top: 6px; 28 | margin-right: 5px; 29 | } 30 | .hide { 31 | display: none; 32 | } 33 | .center { 34 | text-align: center; 35 | margin: 0 auto 0 auto; 36 | } 37 | .logo { 38 | } 39 | .entypo { 40 | font-family: "EntypoRegular", sans-serif; 41 | font-size: 28px; 42 | } 43 | .entypo-social { 44 | font-family: "EntypoSocial", arial; 45 | font-size: 64px; 46 | color: #7e7e7e; 47 | } 48 | a.entypo-social:hover { 49 | color: #a9a9a9; 50 | } 51 | table, a.thumbnail, ul.nav-stacked { 52 | background: #fefefe; 53 | } 54 | label.error { 55 | color: #b20000 56 | } 57 | div.caption { 58 | color: #818181; 59 | } 60 | a.cover { 61 | height: 270px; 62 | overflow: hidden; 63 | } 64 | a.thumbnail > img { 65 | width: 160px; 66 | height: 160px; 67 | } 68 | img.media-object.event { 69 | width: 64px; 70 | height: 64px; 71 | } 72 | div.browse-content { 73 | margin-bottom: 64px; 74 | } 75 | div#player { 76 | font-family: "EntypoRegular", sans-serif; 77 | background: #fefefe; 78 | border-top: 1px solid #dddddd; 79 | height: 48px; 80 | opacity: 0.85; 81 | filter:alpha(opacity=75); 82 | } 83 | div.controls { 84 | font-size: 32px; 85 | } 86 | div#player-content { 87 | margin-top: 16px; 88 | } 89 | div#player-playing { 90 | margin: 0 10px 0 0; 91 | } 92 | .control { 93 | cursor: pointer; 94 | } 95 | footer { 96 | margin: 0 auto 20px auto; 97 | width: 100%; 98 | color: #b3b3b3; 99 | text-align: center; /* center align it with the container */ 100 | } 101 | div#login { 102 | color: #8a8a8a; 103 | font-size: 24px; 104 | text-align: center; 105 | margin: 10px auto 0 auto; 106 | } 107 | div#nav-left { 108 | margin: 0 10px 10px 0; 109 | } 110 | div.page-toolbar { 111 | margin: 0 0 5px 0; 112 | } 113 | .errorlist { 114 | float: right; 115 | list-style-type: none; 116 | color: #b20000 117 | } 118 | .help-inline { 119 | font-size: 14px; 120 | } 121 | div.section { 122 | padding: 10px; 123 | position: relative; 124 | background: #f3f4f6; 125 | min-height: 50px; 126 | background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4); 127 | background-image: -ms-linear-gradient(top, #fefefe, #f4f4f4); 128 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f4f4f4)); 129 | background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4); 130 | background-image: -o-linear-gradient(top, #fefefe, #f4f4f4); 131 | background-image: linear-gradient(top, #fefefe, #f4f4f4); 132 | background-repeat: repeat-x; 133 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fefefe', endColorstr='#f4f4f4', GradientType=0); 134 | background-color: #eff0f3; 135 | background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4); 136 | background-image: -ms-linear-gradient(top, #fefefe, #f4f4f4); 137 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f4f4f4)); 138 | background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4); 139 | background-image: -o-linear-gradient(top, #fefefe, #f4f4f4); 140 | background-image: linear-gradient(top, #fefefe, #f4f4f4); 141 | background-repeat: repeat-x; 142 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fefefe', endColorstr='#f4f4f4', GradientType=0); 143 | border: 1px solid #d5d5d5; 144 | -webkit-border-radius: 10px; 145 | -moz-border-radius: 10px; 146 | border-radius: 10px; 147 | -webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); 148 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); 149 | } 150 | div.section table { 151 | background: #fefefe; 152 | } 153 | div.section table td.status { 154 | text-align: center; 155 | } 156 | div.section-title { 157 | padding: 10px 10px 20px 10px; 158 | margin: 0 -10px 15px -10px; 159 | border-bottom: 1px solid #d5d5d5; 160 | font-size: 20px; 161 | background-image: -moz-linear-gradient(top, #fefeff, #fafafa); 162 | background-image: -ms-linear-gradient(top, #fefeff, #fafafa); 163 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefeff), to(#fafafa)); 164 | background-image: -webkit-linear-gradient(top, #fefeff, #fafafa); 165 | background-image: -o-linear-gradient(top, #fefeff, #fafafa); 166 | background-image: linear-gradient(top, #fefeff, #fafafa); 167 | background-repeat: repeat-x; 168 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fefeff', endColorstr='#fafafa', GradientType=0); 169 | background-color: #eff0f3; 170 | background-image: -moz-linear-gradient(top, #fefeff, #fafafa); 171 | background-image: -ms-linear-gradient(top, #fefeff, #fafafa); 172 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefeff), to(#fafafa)); 173 | background-image: -webkit-linear-gradient(top, #fefeff, #fafafa); 174 | background-image: -o-linear-gradient(top, #fefeff, #fafafa); 175 | background-image: linear-gradient(top, #fefeff, #fafafa); 176 | background-repeat: repeat-x; 177 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fefeff', endColorstr='#fafafa', GradientType=0); 178 | } 179 | div.section-toolbar { 180 | margin: 0 0 10px 0; 181 | } 182 | div.modal.span10 { 183 | position: fixed; 184 | left: 10%; 185 | } 186 | div.toolbar { 187 | margin: 0 0 10px 0; 188 | } 189 | div.caption { 190 | text-align: center; 191 | color: #5e5e5e; 192 | } 193 | .entypo.large { 194 | color: #7a7a7a; 195 | margin: 20px 0 0 0; 196 | font-size: 72px; 197 | text-align: center; 198 | } 199 | div.group.title { 200 | margin: 5px 0 0 0; 201 | font-size: 24px; 202 | color: #7a7a7a; 203 | } 204 | div.social-auth { 205 | width: 25%; 206 | margin: 30px auto 0 auto; 207 | text-align: center; 208 | } 209 | span#credential-status { 210 | margin-left: 20px; 211 | } 212 | div.credential-groups { 213 | margin: 45px 0 0 0; 214 | } 215 | div.credentials { 216 | margin: 25px 0 0 0; 217 | } 218 | a.thumbnail { 219 | color: #8e8e8e; 220 | } 221 | a.thumbnail:hover { 222 | color: #cacaca; 223 | } 224 | a.thumbnail > div.entypo { 225 | margin: 15px 0 10px 0; 226 | font-size: 72px; 227 | text-align: center; 228 | } 229 | .logo { 230 | margin: 32px 48px 0 0; 231 | } 232 | .slogan { 233 | font-size: 32px; 234 | margin: 24px 0 24px 0; 235 | } 236 | .btn-join { 237 | border-radius: 25px; 238 | -webkit-border-radius: 25px; 239 | -moz-border-radius: 25px; 240 | width: 125px; 241 | } 242 | div#footer { 243 | width: 100%; 244 | color: #b3b3b3; 245 | margin: 25px 0 0 0; 246 | padding: 10px 0 0 0; 247 | height: 75px; 248 | } 249 | div#footer-info { 250 | width: 100%; 251 | } 252 | div#copyright { 253 | font-family: "EntypoRegular", sans-serif; 254 | font-size: 12px; 255 | text-align: center; 256 | width: 100%; 257 | } 258 | div#copyright a { 259 | text-decoration: none; 260 | } 261 | ul.footer-links { 262 | text-align: center; 263 | list-style-type: none; 264 | margin: 5px 0 20px 0; 265 | font-size: 48px; 266 | } 267 | ul.footer-links li { 268 | display: inline; 269 | } 270 | ul.footer-links li a { 271 | color: #a1a1a1; 272 | text-decoration: none; 273 | } 274 | ul.footer-links li a:hover { 275 | color: #cacaca; 276 | } 277 | form#signup { 278 | text-align: left; 279 | } 280 | div#activate { 281 | width: 75%; 282 | } 283 | #btn-activate { 284 | text-align: center; 285 | margin: 0 auto; } 286 | .pro-label { 287 | margin: 0 2px 0 3px; 288 | } 289 | .app-info { 290 | margin: 0 0 0 10px; 291 | font-size: 12px; 292 | } 293 | .cart-info { 294 | margin: 0 0 0 20px; 295 | font-size: 14px; 296 | } 297 | .cart-info .entypo { 298 | position: relative; 299 | top: 3px; 300 | } 301 | div.404 { 302 | margin: 25px 0 0 0; 303 | font-size: 48px; 304 | } 305 | -------------------------------------------------------------------------------- /static/js/zc.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * zeroclipboard 3 | * The Zero Clipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie, and a JavaScript interface. 4 | * Copyright 2012 Jon Rohan, James M. Greene, . 5 | * Released under the MIT license 6 | * http://jonrohan.github.com/ZeroClipboard/ 7 | * v1.1.7 8 | */(function(){"use strict";var a=function(a,b){var c=a.style[b];a.currentStyle?c=a.currentStyle[b]:window.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));if(c=="auto"&&b=="cursor"){var d=["a"];for(var e=0;e=0?"&":"?")+"nocache="+(new Date).getTime()},i=function(a){var b=[];return a.trustedDomains&&(typeof a.trustedDomains=="string"?b.push("trustedDomain="+a.trustedDomains):b.push("trustedDomain="+a.trustedDomains.join(","))),b.join("&")},j=function(a,b){if(b.indexOf)return b.indexOf(a);for(var c=0,d=b.length;c ';b=document.createElement("div"),b.id="global-zeroclipboard-html-bridge",b.setAttribute("class","global-zeroclipboard-container"),b.setAttribute("data-clipboard-ready",!1),b.style.position="absolute",b.style.left="-9999px",b.style.top="-9999px",b.style.width="15px",b.style.height="15px",b.style.zIndex="9999",b.innerHTML=c,document.body.appendChild(b)}a.htmlBridge=b,a.flashBridge=document["global-zeroclipboard-flash-bridge"]||b.children[0].lastElementChild};l.prototype.resetBridge=function(){this.htmlBridge.style.left="-9999px",this.htmlBridge.style.top="-9999px",this.htmlBridge.removeAttribute("title"),this.htmlBridge.removeAttribute("data-clipboard-text"),f(m,this.options.activeClass),m=null,this.options.text=null},l.prototype.ready=function(){var a=this.htmlBridge.getAttribute("data-clipboard-ready");return a==="true"||a===!0},l.prototype.reposition=function(){if(!m)return!1;var a=g(m);this.htmlBridge.style.top=a.top+"px",this.htmlBridge.style.left=a.left+"px",this.htmlBridge.style.width=a.width+"px",this.htmlBridge.style.height=a.height+"px",this.htmlBridge.style.zIndex=a.zIndex+1,this.setSize(a.width,a.height)},l.dispatch=function(a,b){l.prototype._singleton.receiveEvent(a,b)},l.prototype.on=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d 3 | 4 | 5 | 6 | {{APP_NAME}}{% block title %}{% endblock %} 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% block extra_head_css %}{% endblock %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% block extra_head_js %}{% endblock %} 31 | 32 | 33 | {% if request.user.is_authenticated and not ENCRYPTION_KEY %} 34 | 55 | {% endif %} 56 |
57 | {% if messages %} 58 | {% for message in messages %} 59 |
60 | × 61 |

{{message}}

62 |

63 | {% endfor %} 64 | 67 | {% endif %} 68 |
69 | 110 | 111 | {% block base_content %} 112 |
113 | {% block main_content %}{% endblock %} 114 |
115 | {% endblock %} 116 | 117 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | {% block extra_js %}{% endblock %} 136 | 192 | {% if GOOGLE_ANALYTICS_CODE %} 193 | 204 | {% endif %} 205 | {% if INTERCOM_APP_ID %} 206 | 213 | 214 | 215 | {% endif %} 216 | 217 | 218 | -------------------------------------------------------------------------------- /locksmith/api_v1.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from tastypie.resources import ModelResource 15 | from tastypie.authorization import (DjangoAuthorization, Authorization) 16 | from tastypie.authentication import ApiKeyAuthentication, Authentication 17 | from tastypie.utils import trailing_slash 18 | from tastypie.bundle import Bundle 19 | from tastypie import fields 20 | from django.core.urlresolvers import reverse 21 | from django.conf.urls import patterns, url, include 22 | from django.core.paginator import Paginator, InvalidPage 23 | from django.http import Http404, HttpResponse 24 | from django.contrib.auth.models import User 25 | from django.contrib.auth import authenticate, login as login_user 26 | from django.views.decorators.csrf import csrf_exempt 27 | from django.conf import settings 28 | from django.core.cache import cache 29 | from django.db.models import Q 30 | from vault.models import CredentialGroup, Credential 31 | from tastypie.models import ApiKey 32 | from tastypie.constants import ALL, ALL_WITH_RELATIONS 33 | from utils.encryption import (decrypt, set_user_encryption_key, 34 | get_user_encryption_key) 35 | import simplejson as json 36 | import os 37 | 38 | # set csrf exempt to allow mobile login 39 | @csrf_exempt 40 | def api_login(request): 41 | username = request.POST.get('username') 42 | password = request.POST.get('password') 43 | if not username: 44 | # attempt to parse a json string 45 | data = json.loads(request.body) 46 | username = data.get('username') 47 | password = data.get('password') 48 | user = authenticate(username=username, password=password) 49 | code = 200 50 | if user is not None: 51 | login_user(request, user) 52 | ur = UserResource() 53 | user_data = ur.obj_get(request, username=user.username) 54 | bundle = ur.build_bundle(obj=user_data, request=request) 55 | data = ur.serialize(None, ur.full_dehydrate(bundle), 56 | 'application/json') 57 | else: 58 | data = json.dumps({'error': 'Access denied'}) 59 | code = 403 60 | return HttpResponse(data, status=code, 61 | content_type='application/json') 62 | 63 | class AppAuthentication(Authentication): 64 | def is_authenticated(self, request, **kwargs): 65 | # session based 66 | if request.user.is_authenticated(): 67 | return True 68 | else: # check api_key 69 | if request.META.has_key('HTTP_AUTHORIZATION'): 70 | auth_header = request.META.get('HTTP_AUTHORIZATION') 71 | key = request.META.get('HTTP_ENCRYPTION_KEY') 72 | try: 73 | username, api_key = auth_header.split()[-1].split(':') 74 | # check auth 75 | user = User.objects.get(username=username) 76 | if user and user.api_key.key == api_key: 77 | # set encryption key 78 | set_user_encryption_key(user.username, key) 79 | # auth successful ; set request.user to user for 80 | # later user (authorization, filtering, etc.) 81 | request.user = user 82 | return True 83 | except: 84 | # invalid auth header 85 | pass 86 | return False 87 | 88 | class CredentialGroupAuthorization(Authorization): 89 | def read_list(self, object_list, bundle): 90 | return object_list.filter(Q(owner=bundle.request.user) | \ 91 | Q(members__in=bundle.request.user)) 92 | 93 | def read_detail(self, object_list, bundle): 94 | return object_list.filter(Q(owner=bundle.request.user) | \ 95 | Q(members__in=[bundle.request.user])) 96 | 97 | def create_list(self, object_list, bundle): 98 | return object_list 99 | 100 | def create_detail(self, object_list, bundle): 101 | return bundle.obj.owner == bundle.request.user 102 | 103 | def update_list(self, object_list, bundle): 104 | allowed = [] 105 | 106 | # Since they may not all be saved, iterate over them. 107 | for obj in object_list: 108 | if obj.owner == bundle.request.user: 109 | allowed.append(obj) 110 | return allowed 111 | 112 | def update_detail(self, object_list, bundle): 113 | return bundle.obj.owner == bundle.request.user 114 | 115 | def delete_list(self, object_list, bundle): 116 | return bundle.obj.owner == bundle.request.user 117 | 118 | def delete_detail(self, object_list, bundle): 119 | return bundle.obj.owner == bundle.request.user 120 | 121 | class CredentialAuthorization(Authorization): 122 | def read_list(self, object_list, bundle): 123 | return object_list.filter(groups__owner=bundle.request.user) 124 | 125 | def read_detail(self, object_list, bundle): 126 | return object_list.filter(groups__owner=bundle.request.user) 127 | 128 | def create_list(self, object_list, bundle): 129 | return object_list 130 | 131 | def create_detail(self, object_list, bundle): 132 | return object_list.filter(groups__owner=bundle.request.user) 133 | 134 | def update_list(self, object_list, bundle): 135 | allowed = [] 136 | 137 | # Since they may not all be saved, iterate over them. 138 | for obj in object_list: 139 | for g in obj.groups: 140 | if g.owner == bundle.request.user: 141 | allowed.append(obj) 142 | return allowed 143 | 144 | def update_detail(self, object_list, bundle): 145 | return object_list.filter(groups__owner=bundle.request.user) 146 | 147 | def delete_list(self, object_list, bundle): 148 | return object_list.filter(groups__owner=bundle.request.user) 149 | 150 | def delete_detail(self, object_list, bundle): 151 | return object_list.filter(groups__owner=bundle.request.user) 152 | 153 | class UserResource(ModelResource): 154 | class Meta: 155 | queryset = User.objects.all() 156 | excludes = ('id', 'password', 'is_staff', 'is_superuser') 157 | list_allowed_methods = ['get'] 158 | authentication = AppAuthentication() 159 | authorization = Authorization() 160 | resource_name = 'accounts' 161 | 162 | def prepend_urls(self): 163 | return [ 164 | url(r"^(?P%s)/(?P[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), 165 | ] 166 | 167 | def get_object_list(self, request): 168 | return super(UserResource, self).get_object_list(request).filter( 169 | username=request.user.username) 170 | 171 | # only let non-admin users see their own account 172 | def apply_authorization_limits(self, request, object_list): 173 | if not request.user.is_superuser: 174 | object_list = object_list.filter(username=request.user.username) 175 | return object_list 176 | 177 | # this is broken in tastypie 0.9.13 178 | ## build custom resource_uri (instead of /resource//) 179 | #def get_resource_uri(self, bundle_or_obj, url_name='api_dispatch_list'): 180 | # kwargs = { 181 | # 'resource_name': self._meta.resource_name, 182 | # } 183 | # if isinstance(bundle_or_obj, Bundle): 184 | # kwargs['pk'] = bundle_or_obj.obj.username 185 | # else: 186 | # kwargs['pk'] = bundle_or_obj.id 187 | # if self._meta.api_name is not None: 188 | # kwargs['api_name'] = self._meta.api_name 189 | # return self._build_reverse_url('api_dispatch_detail', kwargs = kwargs) 190 | 191 | def dehydrate(self, bundle): 192 | # add api_key 193 | bundle.data['api_key'] = bundle.obj.api_key.key 194 | return bundle 195 | 196 | class CredentialGroupResource(ModelResource): 197 | class Meta: 198 | queryset = CredentialGroup.objects.all() 199 | excludes = ('id', ) 200 | #list_allowed_methods = ['get'] 201 | authentication = AppAuthentication() 202 | authorization = CredentialGroupAuthorization() 203 | resource_name = 'credentialgroups' 204 | filtering = { 205 | "name": ALL, 206 | "description": ALL, 207 | } 208 | 209 | def prepend_urls(self): 210 | return [ 211 | url(r"^(?P%s)/(?P[\w\d_.-]+)/$" \ 212 | % self._meta.resource_name, self.wrap_view('dispatch_detail'), 213 | name="api_dispatch_detail"), 214 | ] 215 | 216 | def apply_authorization_limits(self, request, object_list): 217 | if not request.user.is_superuser: 218 | object_list = object_list.filter(owner=request.user) 219 | return object_list 220 | 221 | # this is broken in tastypie 0.9.13 222 | # build custom resource_uri (instead of /resource//) 223 | #def get_resource_uri(self, bundle_or_obj, url_name='api_dispatch_list'): 224 | # kwargs = { 225 | # 'resource_name': self._meta.resource_name, 226 | # } 227 | # if isinstance(bundle_or_obj, Bundle): 228 | # kwargs['pk'] = bundle_or_obj.obj.uuid 229 | # else: 230 | # kwargs['pk'] = bundle_or_obj.id 231 | # if self._meta.api_name is not None: 232 | # kwargs['api_name'] = self._meta.api_name 233 | # return self._build_reverse_url('api_dispatch_detail', kwargs = kwargs) 234 | 235 | def obj_create(self, bundle, **kwargs): 236 | # set the owner 237 | kwargs['owner'] = bundle.request.user 238 | return super(CredentialGroupResource, self).obj_create(bundle, **kwargs) 239 | 240 | class CredentialResource(ModelResource): 241 | groups = fields.ToManyField(CredentialGroupResource, 'groups', full=True) 242 | 243 | class Meta: 244 | queryset = Credential.objects.all() 245 | excludes = ('id', ) 246 | #list_allowed_methods = ['get'] 247 | authentication = AppAuthentication() 248 | authorization = CredentialAuthorization() 249 | resource_name = 'credentials' 250 | pass_request_user_to_django = True 251 | filtering = { 252 | "name": ALL, 253 | "description": ALL, 254 | "url": ALL, 255 | } 256 | 257 | def prepend_urls(self): 258 | return [ 259 | url(r"^(?P%s)/(?P[\w\d_.-]+)/$" % self._meta.resource_name, 260 | self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), 261 | ] 262 | 263 | # this is broken in tastypie 0.9.13 264 | # build custom resource_uri (instead of /resource//) 265 | #def get_resource_uri(self, bundle_or_obj, url_name='api_dispatch_list'): 266 | # kwargs = { 267 | # 'resource_name': self._meta.resource_name, 268 | # } 269 | # if isinstance(bundle_or_obj, Bundle): 270 | # kwargs['pk'] = bundle_or_obj.obj.uuid 271 | # else: 272 | # kwargs['pk'] = bundle_or_obj.id 273 | # if self._meta.api_name is not None: 274 | # kwargs['api_name'] = self._meta.api_name 275 | # return self._build_reverse_url('api_dispatch_detail', kwargs = kwargs) 276 | 277 | #def apply_authorization_limits(self, request, object_list): 278 | # return object_list.filter(owner=request.user) 279 | 280 | def dehydrate(self, bundle): 281 | u = bundle.request.user 282 | key = get_user_encryption_key(u.username) 283 | try: 284 | bundle.data['password'] = decrypt(bundle.data['password'], 285 | key) 286 | except: 287 | bundle.data['password'] = None 288 | return bundle 289 | -------------------------------------------------------------------------------- /vault/templates/vault/group.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | {% load locksmith %} 4 | 5 | {% block main_content %} 6 | {% if group.owner == request.user %} 7 | 33 | 81 | {% endif %} 82 | 136 |
137 | {% if group.owner == request.user %} 138 | {% trans 'Add Credential' %} 139 | {% trans 'Edit Group' %} 140 | {% trans 'Delete Group' %} 141 | {% endif %} 142 |
143 |
{{group.name}}
144 |

{{group.description|default:""}}

145 |
146 | {% with credentials=group.get_credentials %} 147 | {% if credentials %} 148 | 160 |
161 | {% else %} 162 |

{% trans 'No credentials' %}

163 | {% endif %} 164 | {% endwith %} 165 | 268 | {% endblock %} 269 | -------------------------------------------------------------------------------- /static/js/holder.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Holder - 1.9 - client side image placeholders 4 | (c) 2012-2013 Ivan Malopinsky / http://imsky.co 5 | 6 | Provided under the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 7 | Commercial use requires attribution. 8 | 9 | */ 10 | 11 | var Holder = Holder || {}; 12 | (function (app, win) { 13 | 14 | var preempted = false, 15 | fallback = false, 16 | canvas = document.createElement('canvas'); 17 | 18 | //getElementsByClassName polyfill 19 | document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i 1) { 68 | text_height = template.size / (ctx.measureText(text).width / width); 69 | } 70 | //Resetting font size if necessary 71 | ctx.font = "bold " + (text_height * ratio) + "px "+font; 72 | ctx.fillText(text, (width / 2), (height / 2), width); 73 | return canvas.toDataURL("image/png"); 74 | } 75 | 76 | function render(mode, el, holder, src) { 77 | var dimensions = holder.dimensions, 78 | theme = holder.theme, 79 | text = holder.text ? decodeURIComponent(holder.text) : holder.text; 80 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 81 | theme = (text ? extend(theme, { text: text }) : theme); 82 | theme = (holder.font ? extend(theme, {font: holder.font}) : theme); 83 | 84 | var ratio = 1; 85 | if(window.devicePixelRatio && window.devicePixelRatio > 1){ 86 | ratio = window.devicePixelRatio; 87 | } 88 | 89 | if (mode == "image") { 90 | el.setAttribute("data-src", src); 91 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 92 | 93 | if(fallback || !holder.auto){ 94 | el.style.width = dimensions.width + "px"; 95 | el.style.height = dimensions.height + "px"; 96 | } 97 | 98 | if (fallback) { 99 | el.style.backgroundColor = theme.background; 100 | 101 | } 102 | else{ 103 | el.setAttribute("src", draw(ctx, dimensions, theme, ratio)); 104 | } 105 | } else { 106 | if (!fallback) { 107 | el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")"; 108 | el.style.backgroundSize = dimensions.width+"px "+dimensions.height+"px"; 109 | } 110 | } 111 | }; 112 | 113 | function fluid(el, holder, src) { 114 | var dimensions = holder.dimensions, 115 | theme = holder.theme, 116 | text = holder.text; 117 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 118 | theme = (text ? extend(theme, { 119 | text: text 120 | }) : theme); 121 | 122 | var fluid = document.createElement("div"); 123 | 124 | fluid.style.backgroundColor = theme.background; 125 | fluid.style.color = theme.foreground; 126 | fluid.className = el.className + " holderjs-fluid"; 127 | fluid.style.width = holder.dimensions.width + (holder.dimensions.width.indexOf("%")>0?"":"px"); 128 | fluid.style.height = holder.dimensions.height + (holder.dimensions.height.indexOf("%")>0?"":"px"); 129 | fluid.id = el.id; 130 | 131 | el.style.width=0; 132 | el.style.height=0; 133 | 134 | if (theme.text) { 135 | fluid.appendChild(document.createTextNode(theme.text)) 136 | } else { 137 | fluid.appendChild(document.createTextNode(dimensions_caption)) 138 | fluid_images.push(fluid); 139 | setTimeout(fluid_update, 0); 140 | } 141 | 142 | el.parentNode.insertBefore(fluid, el.nextSibling) 143 | 144 | if(window.jQuery){ 145 | jQuery(function($){ 146 | $(el).on("load", function(){ 147 | el.style.width = fluid.style.width; 148 | el.style.height = fluid.style.height; 149 | $(el).show(); 150 | $(fluid).remove(); 151 | }); 152 | }) 153 | } 154 | } 155 | 156 | function fluid_update() { 157 | for (i in fluid_images) { 158 | if(!fluid_images.hasOwnProperty(i)) continue; 159 | var el = fluid_images[i], 160 | label = el.firstChild; 161 | 162 | el.style.lineHeight = el.offsetHeight+"px"; 163 | label.data = el.offsetWidth + "x" + el.offsetHeight; 164 | } 165 | } 166 | 167 | function parse_flags(flags, options) { 168 | 169 | var ret = { 170 | theme: settings.themes.gray 171 | }, render = false; 172 | 173 | for (sl = flags.length, j = 0; j < sl; j++) { 174 | var flag = flags[j]; 175 | if (app.flags.dimensions.match(flag)) { 176 | render = true; 177 | ret.dimensions = app.flags.dimensions.output(flag); 178 | } else if (app.flags.fluid.match(flag)) { 179 | render = true; 180 | ret.dimensions = app.flags.fluid.output(flag); 181 | ret.fluid = true; 182 | } else if (app.flags.colors.match(flag)) { 183 | ret.theme = app.flags.colors.output(flag); 184 | } else if (options.themes[flag]) { 185 | //If a theme is specified, it will override custom colors 186 | ret.theme = options.themes[flag]; 187 | } else if (app.flags.text.match(flag)) { 188 | ret.text = app.flags.text.output(flag); 189 | } else if(app.flags.font.match(flag)){ 190 | ret.font = app.flags.font.output(flag); 191 | } 192 | else if(app.flags.auto.match(flag)){ 193 | ret.auto = true; 194 | } 195 | } 196 | 197 | return render ? ret : false; 198 | 199 | }; 200 | 201 | if (!canvas.getContext) { 202 | fallback = true; 203 | } else { 204 | if (canvas.toDataURL("image/png") 205 | .indexOf("data:image/png") < 0) { 206 | //Android doesn't support data URI 207 | fallback = true; 208 | } else { 209 | var ctx = canvas.getContext("2d"); 210 | } 211 | } 212 | 213 | var fluid_images = []; 214 | 215 | var settings = { 216 | domain: "holder.js", 217 | images: "img", 218 | bgnodes: ".holderjs", 219 | themes: { 220 | "gray": { 221 | background: "#eee", 222 | foreground: "#aaa", 223 | size: 12 224 | }, 225 | "social": { 226 | background: "#3a5a97", 227 | foreground: "#fff", 228 | size: 12 229 | }, 230 | "industrial": { 231 | background: "#434A52", 232 | foreground: "#C2F200", 233 | size: 12 234 | } 235 | }, 236 | stylesheet: ".holderjs-fluid {font-size:16px;font-weight:bold;text-align:center;font-family:sans-serif;margin:0}" 237 | }; 238 | 239 | 240 | app.flags = { 241 | dimensions: { 242 | regex: /^(\d+)x(\d+)$/, 243 | output: function (val) { 244 | var exec = this.regex.exec(val); 245 | return { 246 | width: +exec[1], 247 | height: +exec[2] 248 | } 249 | } 250 | }, 251 | fluid: { 252 | regex: /^([0-9%]+)x([0-9%]+)$/, 253 | output: function (val) { 254 | var exec = this.regex.exec(val); 255 | return { 256 | width: exec[1], 257 | height: exec[2] 258 | } 259 | } 260 | }, 261 | colors: { 262 | regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, 263 | output: function (val) { 264 | var exec = this.regex.exec(val); 265 | return { 266 | size: settings.themes.gray.size, 267 | foreground: "#" + exec[2], 268 | background: "#" + exec[1] 269 | } 270 | } 271 | }, 272 | text: { 273 | regex: /text\:(.*)/, 274 | output: function (val) { 275 | return this.regex.exec(val)[1]; 276 | } 277 | }, 278 | font: { 279 | regex: /font\:(.*)/, 280 | output: function(val){ 281 | return this.regex.exec(val)[1]; 282 | } 283 | }, 284 | auto: { 285 | regex: /^auto$/ 286 | } 287 | } 288 | 289 | for (var flag in app.flags) { 290 | if(!app.flags.hasOwnProperty(flag)) continue; 291 | app.flags[flag].match = function (val) { 292 | return val.match(this.regex) 293 | } 294 | } 295 | 296 | app.add_theme = function (name, theme) { 297 | name != null && theme != null && (settings.themes[name] = theme); 298 | return app; 299 | }; 300 | 301 | app.add_image = function (src, el) { 302 | var node = selector(el); 303 | if (node.length) { 304 | for (var i = 0, l = node.length; i < l; i++) { 305 | var img = document.createElement("img") 306 | img.setAttribute("data-src", src); 307 | node[i].appendChild(img); 308 | } 309 | } 310 | return app; 311 | }; 312 | 313 | app.run = function (o) { 314 | var options = extend(settings, o), images = []; 315 | 316 | if(options.images instanceof window.NodeList){ 317 | imageNodes = options.images; 318 | } 319 | else if(options.images instanceof window.Node){ 320 | imageNodes = [options.images]; 321 | } 322 | else{ 323 | imageNodes = selector(options.images); 324 | } 325 | 326 | if(options.elements instanceof window.NodeList){ 327 | bgnodes = options.bgnodes; 328 | } 329 | else if(options.bgnodes instanceof window.Node){ 330 | bgnodes = [options.bgnodes]; 331 | } 332 | else{ 333 | bgnodes = selector(options.bgnodes); 334 | } 335 | 336 | preempted = true; 337 | 338 | for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); 339 | 340 | var holdercss = document.getElementById("holderjs-style"); 341 | 342 | if(!holdercss){ 343 | holdercss = document.createElement("style"); 344 | holdercss.setAttribute("id", "holderjs-style"); 345 | holdercss.type = "text/css"; 346 | document.getElementsByTagName("head")[0].appendChild(holdercss); 347 | } 348 | 349 | if(holdercss.styleSheet){ 350 | holdercss.styleSheet += options.stylesheet; 351 | } 352 | else{ 353 | holdercss.textContent+= options.stylesheet; 354 | } 355 | 356 | var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); 357 | 358 | for (var l = bgnodes.length, i = 0; i < l; i++) { 359 | var src = window.getComputedStyle(bgnodes[i], null) 360 | .getPropertyValue("background-image"); 361 | var flags = src.match(cssregex); 362 | if (flags) { 363 | var holder = parse_flags(flags[1].split("/"), options); 364 | if (holder) { 365 | render("background", bgnodes[i], holder, src); 366 | } 367 | } 368 | } 369 | 370 | for (var l = images.length, i = 0; i < l; i++) { 371 | var src = images[i].getAttribute("src") || images[i].getAttribute("data-src"); 372 | if (src != null && src.indexOf(options.domain) >= 0) { 373 | var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) 374 | .split("/"), options); 375 | if (holder) { 376 | if (holder.fluid) { 377 | fluid(images[i], holder, src); 378 | } else { 379 | render("image", images[i], holder, src); 380 | } 381 | } 382 | } 383 | } 384 | return app; 385 | }; 386 | 387 | contentLoaded(win, function () { 388 | if (window.addEventListener) { 389 | window.addEventListener("resize", fluid_update, false); 390 | window.addEventListener("orientationchange", fluid_update, false); 391 | } else { 392 | window.attachEvent("onresize", fluid_update) 393 | } 394 | preempted || app.run(); 395 | }); 396 | 397 | if ( typeof define === "function" && define.amd ) { 398 | define( "Holder", [], function () { return app; } ); 399 | } 400 | 401 | })(Holder, window); 402 | -------------------------------------------------------------------------------- /locksmith/settings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Evan Hazlett and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # Django settings for locksmith project. 15 | import os 16 | import subprocess 17 | PROJECT_ROOT = os.path.join(os.path.dirname(__file__), '../') 18 | 19 | DEBUG = True 20 | TEMPLATE_DEBUG = DEBUG 21 | 22 | APP_NAME = 'locksmith' 23 | # get latest git revision 24 | process = subprocess.Popen(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE) 25 | out, err = process.communicate() 26 | 27 | APP_REVISION = out[:6] 28 | ADMINS = ( 29 | ('Evan Hazlett', 'ejhazlett@gmail.com'), 30 | ) 31 | ADMIN_EMAIL = 'support@vitasso.com' 32 | 33 | AUTH_PROFILE_MODULE = 'accounts.UserProfile' 34 | MANAGERS = ADMINS 35 | 36 | BCRYPT_ENABLED = True 37 | BCRYPT_ROUNDS = 12 38 | BCRYPT_MIGRATE = True 39 | 40 | SENTRY_DSN = '' 41 | SIGNUP_ENABLED = True 42 | 43 | AWS_ACCESS_KEY_ID = '' 44 | AWS_SECRET_ACCESS_KEY = '' 45 | AWS_STORAGE_BUCKET_NAME = 'locksmith' 46 | 47 | CACHE_ENCRYPTION_KEY = '{0}:key' 48 | 49 | 50 | # arcus cloud settings 51 | if 'VCAP_SERVICES' in os.environ: 52 | import json 53 | vcap_services = json.loads(os.environ['VCAP_SERVICES']) 54 | mysql_srv = vcap_services['mysql-5.1'][0] 55 | redis_srv = vcap_services['redis-2.6'][0] 56 | memcached_srv = vcap_services['memcached-1.4'][0] 57 | elasticsearch_srv = vcap_services['elasticsearch-0.19'][0] 58 | mysql_creds = mysql_srv['credentials'] 59 | redis_creds = redis_srv['credentials'] 60 | memcached_creds = memcached_srv['credentials'] 61 | DATABASES = { 62 | 'default': { 63 | 'ENGINE': 'django.db.backends.mysql', 64 | 'NAME': mysql_creds['name'], 65 | 'USER': mysql_creds['user'], 66 | 'PASSWORD': mysql_creds['password'], 67 | 'HOST': mysql_creds['hostname'], 68 | 'PORT': mysql_creds['port'], 69 | } 70 | } 71 | CACHES = { 72 | 'default': { 73 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 74 | 'LOCATION': '{0}:{1}'.format(memcached_creds['host'], 75 | memcached_creds['port']), 76 | } 77 | } 78 | REDIS_HOST = redis_creds['host'] 79 | REDIS_PORT = redis_creds['port'] 80 | REDIS_DB = 0 81 | REDIS_PASSWORD = redis_creds['password'] 82 | RQ_QUEUES = { 83 | 'default': { 84 | 'HOST': REDIS_HOST, 85 | 'PORT': REDIS_PORT, 86 | 'DB': REDIS_DB, 87 | 'PASSWORD': REDIS_PASSWORD, 88 | } 89 | } 90 | else: 91 | DATABASES = { 92 | 'default': { 93 | 'ENGINE': 'django.db.backends.sqlite3', 94 | 'NAME': 'locksmith.db', 95 | 'USER': '', 96 | 'PASSWORD': '', 97 | 'HOST': '', 98 | 'PORT': '', 99 | } 100 | } 101 | CACHES = { 102 | 'default': { 103 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 104 | } 105 | } 106 | REDIS_HOST = '127.0.0.1' 107 | REDIS_PORT = 6739 108 | REDIS_DB = 0 109 | REDIS_PASSWORD = None 110 | RQ_QUEUES = { 111 | 'default': { 112 | 'HOST': REDIS_HOST, 113 | 'PORT': REDIS_PORT, 114 | 'DB': REDIS_DB, 115 | 'PASSWORD': REDIS_PASSWORD, 116 | } 117 | } 118 | 119 | SESSION_EXPIRE_AT_BROWSER_CLOSE = True 120 | 121 | GOOGLE_ANALYTICS_CODE = '' 122 | INTERCOM_APP_ID = '' 123 | STRIPE_API_KEY = '' 124 | STRIPE_PUBLISHABLE_KEY = '' 125 | ACCOUNT_PLAN = 'locksmith-pro' # stripe plan 126 | 127 | # auth backends 128 | AUTHENTICATION_BACKENDS = ( 129 | 'social_auth.backends.twitter.TwitterBackend', 130 | 'social_auth.backends.google.GoogleOAuth2Backend', 131 | 'social_auth.backends.contrib.github.GithubBackend', 132 | 'django.contrib.auth.backends.ModelBackend', 133 | ) 134 | # these are placeholders ; set in local_settings.py to deploy 135 | TWITTER_CONSUMER_KEY = '' 136 | TWITTER_CONSUMER_SECRET = '' 137 | FACEBOOK_APP_ID = '' 138 | FACEBOOK_API_SECRET = '' 139 | LINKEDIN_CONSUMER_KEY = '' 140 | LINKEDIN_CONSUMER_SECRET = '' 141 | ORKUT_CONSUMER_KEY = '' 142 | ORKUT_CONSUMER_SECRET = '' 143 | GOOGLE_CONSUMER_KEY = '' 144 | GOOGLE_CONSUMER_SECRET = '' 145 | GOOGLE_OAUTH2_CLIENT_ID = '' 146 | GOOGLE_OAUTH2_CLIENT_SECRET = '' 147 | FOURSQUARE_CONSUMER_KEY = '' 148 | FOURSQUARE_CONSUMER_SECRET = '' 149 | VK_APP_ID = '' 150 | VK_API_SECRET = '' 151 | LIVE_CLIENT_ID = '' 152 | LIVE_CLIENT_SECRET = '' 153 | SKYROCK_CONSUMER_KEY = '' 154 | SKYROCK_CONSUMER_SECRET = '' 155 | YAHOO_CONSUMER_KEY = '' 156 | YAHOO_CONSUMER_SECRET = '' 157 | READABILITY_CONSUMER_SECRET = '' 158 | READABILITY_CONSUMER_SECRET = '' 159 | GITHUB_APP_ID = '' 160 | GITHUB_API_SECRET = '' 161 | GITHUB_EXTENDED_PERMISSIONS = ['user', 'user:email'] 162 | 163 | # more social auth settings 164 | #LOGIN_URL = '/login-form/' 165 | LOGIN_URL = '/accounts/login/' 166 | LOGIN_REDIRECT_URL = '/' 167 | LOGIN_ERROR_URL = '/login-error/' 168 | SOCIAL_AUTH_COMPLETE_URL_NAME = 'socialauth_complete' 169 | SOCIAL_AUTH_ASSOCIATE_URL_NAME = 'socialauth_associate_complete' 170 | # needed due to InnoDB storage restriction 171 | # ideally we'd use Postgres, but it has issues in Arcus Cloud at the moment 172 | # see https://github.com/omab/django-social-auth/issues/539 for details 173 | SOCIAL_AUTH_UID_LENGTH = 222 174 | SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH = 200 175 | SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH = 135 176 | SOCIAL_AUTH_ASSOCIATION_HANDLE_LENGTH = 125 177 | 178 | 179 | # Local time zone for this installation. Choices can be found here: 180 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 181 | # although not all choices may be available on all operating systems. 182 | # In a Windows environment this must be set to your system time zone. 183 | TIME_ZONE = 'America/New_York' 184 | 185 | # Language code for this installation. All choices can be found here: 186 | # http://www.i18nguy.com/unicode/language-identifiers.html 187 | LANGUAGE_CODE = 'en-us' 188 | 189 | SITE_ID = 1 190 | 191 | # If you set this to False, Django will make some optimizations so as not 192 | # to load the internationalization machinery. 193 | USE_I18N = True 194 | 195 | # If you set this to False, Django will not format dates, numbers and 196 | # calendars according to the current locale. 197 | USE_L10N = True 198 | 199 | # If you set this to False, Django will not use timezone-aware datetimes. 200 | USE_TZ = True 201 | 202 | # Absolute filesystem path to the directory that will hold user-uploaded files. 203 | # Example: "/home/media/media.lawrence.com/media/" 204 | MEDIA_ROOT = '' 205 | 206 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 207 | # trailing slash. 208 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 209 | MEDIA_URL = '' 210 | 211 | # Absolute path to the directory static files should be collected to. 212 | # Don't put anything in this directory yourself; store your static files 213 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 214 | # Example: "/home/media/media.lawrence.com/static/" 215 | STATIC_ROOT = '' 216 | 217 | # URL prefix for static files. 218 | # Example: "http://media.lawrence.com/static/" 219 | STATIC_URL = '/static/' 220 | 221 | # Additional locations of static files 222 | STATICFILES_DIRS = ( 223 | os.path.join(PROJECT_ROOT, 'static'), 224 | ) 225 | 226 | # List of finder classes that know how to find static files in 227 | # various locations. 228 | STATICFILES_FINDERS = ( 229 | 'django.contrib.staticfiles.finders.FileSystemFinder', 230 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 231 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 232 | ) 233 | 234 | # Make this unique, and don't share it with anybody. 235 | SECRET_KEY = 'z)h81*4eitd6k=8%&i164h0fukf3p(fe8cpo*g&vc2h@n8aba%' 236 | 237 | # List of callables that know how to import templates from various sources. 238 | TEMPLATE_LOADERS = ( 239 | 'django.template.loaders.filesystem.Loader', 240 | 'django.template.loaders.app_directories.Loader', 241 | # 'django.template.loaders.eggs.Loader', 242 | ) 243 | TEMPLATE_CONTEXT_PROCESSORS = ( 244 | "django.contrib.auth.context_processors.auth", 245 | "django.core.context_processors.debug", 246 | "django.core.context_processors.i18n", 247 | "django.core.context_processors.media", 248 | "django.core.context_processors.static", 249 | "django.core.context_processors.request", 250 | "django.core.context_processors.tz", 251 | "django.contrib.messages.context_processors.messages", 252 | "locksmith.context_processors.app_info", 253 | "locksmith.context_processors.google_analytics_code", 254 | "locksmith.context_processors.encryption_key", 255 | "locksmith.context_processors.intercom_app_id", 256 | "locksmith.context_processors.signup_enabled", 257 | "locksmith.context_processors.stripe_info", 258 | 'social_auth.context_processors.social_auth_by_name_backends', 259 | 'social_auth.context_processors.social_auth_backends', 260 | 'social_auth.context_processors.social_auth_login_redirect', 261 | ) 262 | 263 | MIDDLEWARE_CLASSES = ( 264 | 'django.middleware.common.CommonMiddleware', 265 | 'django.contrib.sessions.middleware.SessionMiddleware', 266 | 'django.middleware.csrf.CsrfViewMiddleware', 267 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 268 | 'django.contrib.messages.middleware.MessageMiddleware', 269 | 'locksmith.middleware.threadlocal.ThreadLocalMiddleware', 270 | # Uncomment the next line for simple clickjacking protection: 271 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 272 | ) 273 | 274 | ROOT_URLCONF = 'locksmith.urls' 275 | 276 | # Python dotted path to the WSGI application used by Django's runserver. 277 | WSGI_APPLICATION = 'locksmith.wsgi.application' 278 | 279 | TEMPLATE_DIRS = ( 280 | os.path.join(PROJECT_ROOT, 'templates'), 281 | ) 282 | 283 | INSTALLED_APPS = ( 284 | 'django.contrib.auth', 285 | 'django.contrib.contenttypes', 286 | 'django.contrib.sessions', 287 | 'django.contrib.sites', 288 | 'django.contrib.messages', 289 | 'django.contrib.staticfiles', 290 | 'django.contrib.admin', 291 | 'social_auth', 292 | 'django_forms_bootstrap', 293 | 'south', 294 | 'tastypie', 295 | 'django_bcrypt', 296 | 'locksmith', 297 | 'accounts', 298 | 'vault', 299 | ) 300 | 301 | # A sample logging configuration. The only tangible logging 302 | # performed by this configuration is to send an email to 303 | # the site admins on every HTTP 500 error when DEBUG=False. 304 | # See http://docs.djangoproject.com/en/dev/topics/logging for 305 | # more details on how to customize your logging configuration. 306 | LOGGING = { 307 | 'version': 1, 308 | 'disable_existing_loggers': False, 309 | 'root': { 310 | 'level': 'WARNING', 311 | 'handlers': ['console', 'sentry'], 312 | }, 313 | 'formatters': { 314 | 'verbose': { 315 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 316 | }, 317 | }, 318 | 'filters': { 319 | 'require_debug_false': { 320 | '()': 'django.utils.log.RequireDebugFalse' 321 | } 322 | }, 323 | 'handlers': { 324 | 'console': { 325 | 'level': 'DEBUG', 326 | 'class': 'logging.StreamHandler', 327 | 'formatter': 'verbose' 328 | }, 329 | 'sentry': { 330 | 'level': 'ERROR', 331 | 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 332 | }, 333 | 'mail_admins': { 334 | 'level': 'ERROR', 335 | 'filters': ['require_debug_false'], 336 | 'class': 'django.utils.log.AdminEmailHandler' 337 | } 338 | }, 339 | 'loggers': { 340 | 'django.request': { 341 | 'handlers': ['mail_admins'], 342 | 'level': 'ERROR', 343 | 'propagate': True, 344 | }, 345 | 'raven': { 346 | 'level': 'DEBUG', 347 | 'handlers': ['console'], 348 | 'propagate': False, 349 | }, 350 | 'sentry.errors': { 351 | 'level': 'DEBUG', 352 | 'handlers': ['console'], 353 | 'propagate': False, 354 | }, 355 | } 356 | } 357 | 358 | try: 359 | from local_settings import * 360 | except ImportError: 361 | pass 362 | 363 | if AWS_ACCESS_KEY_ID: 364 | STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage' 365 | DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' 366 | 367 | # these must come after the above local_settings import in order to 368 | # check for values in local_settings 369 | if SENTRY_DSN: 370 | INSTALLED_APPS = INSTALLED_APPS + ( 371 | 'raven.contrib.django.raven_compat', 372 | ) 373 | MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( 374 | 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', 375 | ) 376 | 377 | -------------------------------------------------------------------------------- /static/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.0 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | --------------------------------------------------------------------------------