Browse Source

simplify API with simple model password

pull/342/head
Guillaume Vincent 8 years ago
parent
commit
18543b266f
9 changed files with 198 additions and 83 deletions
  1. +8
    -8
      api/admin.py
  2. +4
    -0
      api/models.py
  3. +12
    -0
      api/serializers.py
  4. +2
    -2
      api/tests/factories.py
  5. +89
    -0
      api/tests/tests_passwords.py
  6. +1
    -1
      api/urls.py
  7. +10
    -0
      api/views.py
  8. +0
    -3
      config/config.ini
  9. +72
    -69
      lesspass/settings.py

+ 8
- 8
api/admin.py View File

@@ -47,7 +47,7 @@ class LessPassUserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm

list_display = ('email', 'is_admin', 'column_entries_count')
list_display = ('email', 'is_admin', 'column_passwords_count')
list_filter = ('is_admin', 'is_active')
fieldsets = (
(None, {'fields': ('email', 'password')}),
@@ -64,22 +64,22 @@ class LessPassUserAdmin(BaseUserAdmin):
filter_horizontal = ()

def get_queryset(self, request):
return models.LessPassUser.objects.annotate(entries_count=Count('entries'))
return models.LessPassUser.objects.annotate(passwords_count=Count('passwords'))

def column_entries_count(self, instance):
return instance.entries_count
def column_passwords_count(self, instance):
return instance.passwords_count

column_entries_count.short_description = 'Entries count'
column_entries_count.admin_order_field = 'entries_count'
column_passwords_count.short_description = 'Password count'
column_passwords_count.admin_order_field = 'passwords_count'


class EntryAdmin(admin.ModelAdmin):
class PasswordAdmin(admin.ModelAdmin):
list_display = ('id', 'user',)
search_fields = ('user__email',)
ordering = ('user',)


class PasswordAdmin(admin.ModelAdmin):
class EntryAdmin(admin.ModelAdmin):
list_display = ('id', 'user',)
search_fields = ('user__email',)
ordering = ('user',)


+ 4
- 0
api/models.py View File

@@ -50,6 +50,10 @@ class LessPassUser(AbstractBaseUser):
return True

@property
def is_superuser(self):
return self.is_admin

@property
def is_staff(self):
return self.is_admin



+ 12
- 0
api/serializers.py View File

@@ -56,3 +56,15 @@ class EntrySerializer(serializers.ModelSerializer):
setattr(instance, attr, value)
instance.save()
return instance


class PasswordSerializer(serializers.ModelSerializer):
class Meta:
model = models.Password
fields = ('id', 'login', 'site', 'lowercase', 'uppercase', 'symbol', 'number', 'counter', 'length',
'created', 'modified')
read_only_fields = ('created', 'modified')

def create(self, validated_data):
user = self.context['request'].user
return models.Password.objects.create(user=user, **validated_data)

+ 2
- 2
api/tests/factories.py View File

@@ -9,11 +9,11 @@ class UserFactory(factory.DjangoModelFactory):

email = factory.Sequence(lambda n: 'u{0}@lesspass.com'.format(n))
password = factory.PostGenerationMethodCall('set_password', 'password')
is_staff = False
is_admin = False


class AdminFactory(UserFactory):
is_staff = True
is_admin = True


class PasswordInfoFactory(factory.DjangoModelFactory):


+ 89
- 0
api/tests/tests_passwords.py View File

@@ -0,0 +1,89 @@
from rest_framework.test import APITestCase, APIClient

from api import models
from api.tests import factories


class LogoutApiTestCase(APITestCase):
def test_get_passwords_401(self):
response = self.client.get('/api/passwords/')
self.assertEqual(401, response.status_code)


class LoginApiTestCase(APITestCase):
def setUp(self):
self.user = factories.UserFactory()
self.client = APIClient()
self.client.force_authenticate(user=self.user)

def test_get_empty_passwords(self):
request = self.client.get('/api/passwords/')
self.assertEqual(0, len(request.data['results']))

def test_retrieve_its_own_passwords(self):
password = factories.PasswordFactory(user=self.user)
request = self.client.get('/api/passwords/')
self.assertEqual(1, len(request.data['results']))
self.assertEqual(password.site, request.data['results'][0]['site'])

def test_cant_retrieve_other_passwords(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())
request = self.client.get('/api/passwords/%s/' % not_my_password.id)
self.assertEqual(404, request.status_code)

def test_delete_its_own_passwords(self):
password = factories.PasswordFactory(user=self.user)
self.assertEqual(1, models.Password.objects.all().count())
request = self.client.delete('/api/passwords/%s/' % password.id)
self.assertEqual(204, request.status_code)
self.assertEqual(0, models.Password.objects.all().count())

def test_cant_delete_other_password(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())
self.assertEqual(1, models.Password.objects.all().count())
request = self.client.delete('/api/passwords/%s/' % not_my_password.id)
self.assertEqual(404, request.status_code)
self.assertEqual(1, models.Password.objects.all().count())

def test_create_password(self):
password = {
"site": "lesspass.com",
"login": "test@oslab.fr",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"length": 12
}
self.assertEqual(0, models.Password.objects.count())
self.client.post('/api/passwords/', password)
self.assertEqual(1, models.Password.objects.count())

def test_update_password(self):
password = factories.PasswordFactory(user=self.user)
self.assertNotEqual('facebook.com', password.site)
new_password = {
"site": "facebook.com",
"login": "test@oslab.fr",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"length": 12
}
request = self.client.put('/api/passwords/%s/' % password.id, new_password)
self.assertEqual(200, request.status_code, request.content.decode('utf-8'))
password_updated = models.Password.objects.get(id=password.id)
self.assertEqual('facebook.com', password_updated.site)

def test_cant_update_other_password(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())
self.assertEqual('lesspass.com', not_my_password.site)
new_password = {
"site": "facebook",
}
request = self.client.put('/api/passwords/%s/' % not_my_password.id, new_password)
self.assertEqual(404, request.status_code)
self.assertEqual(1, models.Password.objects.all().count())

+ 1
- 1
api/urls.py View File

@@ -6,11 +6,11 @@ from api import views

router = DefaultRouter()
router.register(r'entries', views.EntryViewSet, base_name='entries')
router.register(r'passwords', views.PasswordViewSet, base_name='passwords')

urlpatterns = [
url(r'^', include(router.urls)),
url(r'^tokens/auth/', rest_framework_jwt.views.obtain_jwt_token),
url(r'^tokens/verify/', rest_framework_jwt.views.verify_jwt_token),
url(r'^tokens/refresh/', rest_framework_jwt.views.refresh_jwt_token),
url(r'^auth/', include('djoser.urls')),
]

+ 10
- 0
api/views.py View File

@@ -39,6 +39,16 @@ class AuthViewSet(viewsets.ViewSet):
return Response(status=status.HTTP_401_UNAUTHORIZED)


class PasswordViewSet(viewsets.ModelViewSet):
serializer_class = serializers.PasswordSerializer
permission_classes = (permissions.IsAuthenticated, IsOwner,)
search_fields = ('site', 'email',)
ordering_fields = ('site', 'email', 'created')

def get_queryset(self):
return models.Password.objects.filter(user=self.request.user)


class EntryViewSet(viewsets.ModelViewSet):
serializer_class = serializers.EntrySerializer
permission_classes = (permissions.IsAuthenticated, IsOwner,)


+ 0
- 3
config/config.ini View File

@@ -1,3 +0,0 @@
[DJANGO]
secret_key = 1*5j2@2s1x12h(vm^yx_e^k5jz)n^l=bre4^tgd1*8m#oo5xxy


+ 72
- 69
lesspass/settings.py View File

@@ -1,37 +1,28 @@
import logging
import os
import random
import sys
import datetime

from smartconfigparser import Config
from envparse import env

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

CONFIG_PATH = os.path.join(BASE_DIR, 'config')
if not os.path.exists(CONFIG_PATH):
os.makedirs(CONFIG_PATH)

CONFIG_FILE = os.path.join(CONFIG_PATH, 'config.ini')
config = Config()
config.read(CONFIG_FILE)
def get_secret_key(secret_key):
if not secret_key:
return "".join([random.choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$^&*(-_=+)") for i in range(50)])
return secret_key


try:
SECRET_KEY = config.get('DJANGO', 'SECRET_KEY')
except:
print('SECRET_KEY not found! Generating a new one...')
import random
SECRET_KEY = env('SECRET_KEY', preprocessor=get_secret_key, default=None)

SECRET_KEY = "".join([random.choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$^&*(-_=+)") for i in range(50)])
if not config.has_section('DJANGO'):
config.add_section('DJANGO')
config.set('DJANGO', 'SECRET_KEY', SECRET_KEY)
with open(CONFIG_FILE, 'wt') as f:
config.write(f)
DEBUG = env.bool('DJANGO_DEBUG', default=True)

DEBUG = config.getboolean('DJANGO', 'DEBUG', False)
ALLOWED_HOSTS = []

ALLOWED_HOSTS = config.getlist('DJANGO', 'ALLOWED_HOSTS', ['localhost', '127.0.0.1', '.lesspass.com'])
ADMIN = [('Guillaume Vincent', 'guillaume@oslab.fr'), ]

INSTALLED_APPS = [
'api.apps.ApiConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -39,16 +30,15 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'djoser'
'api'
]

MIDDLEWARE_CLASSES = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
@@ -75,28 +65,20 @@ WSGI_APPLICATION = 'lesspass.wsgi.application'

DATABASES = {
'default': {
'ENGINE': os.getenv('DATABASE_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.getenv('DATABASE_NAME', 'db.sqlite3'),
'USER': os.getenv('DATABASE_USER', None),
'PASSWORD': os.getenv('DATABASE_PASSWORD', None),
'HOST': os.getenv('DATABASE_HOST', None),
'PORT': os.getenv('DATABASE_PORT', None),
'ENGINE': env('DATABASE_ENGINE', default='django.db.backends.sqlite3'),
'NAME': env('DATABASE_NAME', default=os.path.join(BASE_DIR, 'db.sqlite3')),
'USER': env('DATABASE_USER', default=None),
'PASSWORD': env('DATABASE_PASSWORD', default=None),
'HOST': env('DATABASE_HOST', default=None),
'PORT': env('DATABASE_PORT', default=None),
}
}

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', },
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', },
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', },
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', },
]

LANGUAGE_CODE = 'en-us'
@@ -107,22 +89,52 @@ USE_I18N = True

USE_L10N = True

USE_TZ = False
USE_TZ = True

STATIC_URL = '/static/'

STATIC_ROOT = os.path.join(BASE_DIR, 'www', 'static')

MEDIA_URL = '/media/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'www', 'media')

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
}
},
'root': {
'handlers': ['console'],
'level': env('DJANGO_LOG_LEVEL', default='DEBUG'),
}
}

AUTH_USER_MODEL = 'api.LessPassUser'

JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=12),
'JWT_ALLOW_REFRESH': True,
}

EMAIL_BACKEND = 'django.api.mail.backends.console.EmailBackend'

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20,
'PAGE_SIZE': 50,
'DEFAULT_FILTER_BACKENDS': (
'rest_framework.filters.OrderingFilter',
'rest_framework.filters.SearchFilter',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
@@ -134,31 +146,22 @@ REST_FRAMEWORK = {
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)

JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=12),
'JWT_ALLOW_REFRESH': True,
}
class DisableMigrations(object):
def __contains__(self, item):
return True

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
def __getitem__(self, item):
return "notmigrations"

STATIC_ROOT = os.path.join(BASE_DIR, 'www', 'static')

AUTH_USER_MODEL = 'api.LessPassUser'

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
}
},
'root': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),
}
}
TESTS_IN_PROGRESS = False
if 'test' in sys.argv[1:] or 'jenkins' in sys.argv[1:]:
logging.disable(logging.CRITICAL)
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.MD5PasswordHasher',
)
DEBUG = False
TEMPLATE_DEBUG = False
TESTS_IN_PROGRESS = True
MIGRATION_MODULES = DisableMigrations()

Loading…
Cancel
Save