import webapp2
from webapp2_extras import sessions

from webapp2_extras import auth
from webapp2_extras.appengine.auth import models

from google.appengine.ext.ndb import model

import test_base


class TestAuth(test_base.BaseTestCase):

    def setUp(self):
        super(TestAuth, self).setUp()
        self.register_model('User', models.User)
        self.register_model('UserToken', models.UserToken)
        self.register_model('Unique', models.Unique)

    def _check_token(self, user_id, token, subject='auth'):
        rv = models.UserToken.get(user=user_id, subject=subject, token=token)
        return rv is not None

    def test_get_user_by_session(self):
        app = webapp2.WSGIApplication(config={
            'webapp2_extras.sessions': {
                'secret_key': 'foo',
            }
        })
        req = webapp2.Request.blank('/')
        rsp = webapp2.Response()
        req.app = app
        s = auth.get_store(app=app)
        a = auth.Auth(request=req)
        session_store = sessions.get_store(request=req)

        # This won't work.
        a.set_session_data({})
        self.assertEqual(a.session.get('_user'), None)

        # This won't work.
        a.session['_user'] = {}
        self.assertEqual(a.get_session_data(), None)
        self.assertEqual(a.session.get('_user'), None)

        # Create a user.
        m = models.User
        success, user = m.create_user(auth_id='auth_id',
                                      password_raw='password')

        user_id = user.key.id()

        # Get user with session. An anonymous_user is returned.
        rv = a.get_user_by_session()
        self.assertTrue(rv is None)

        # Login with password. User dict is returned.
        rv = a.get_user_by_password('auth_id', 'password')
        self.assertEqual(rv['user_id'], user_id)

        # Save sessions.
        session_store.save_sessions(rsp)

        # Get user with session. Voila!
        cookies = rsp.headers.get('Set-Cookie')
        req = webapp2.Request.blank('/', headers=[('Cookie', cookies)])
        rsp = webapp2.Response()
        req.app = app
        a = auth.Auth(request=req)

        # only auth_id is returned when there're no
        # custom user attributes defined.
        rv = a.get_user_by_session()
        self.assertEqual(rv['user_id'], user_id)

        # If we call get_user_by_token() now, the same user is returned.
        rv2 = a.get_user_by_token(rv['user_id'], rv['token'])
        self.assertTrue(rv is rv2)

        # Let's get it again and check that token is the same.
        token = rv['token']
        a._user = None
        rv = a.get_user_by_session()
        self.assertEqual(rv['user_id'], user_id)
        self.assertEqual(rv['token'], token)

        # Now let's force token to be renewed and check that we have a new one.
        s.config['token_new_age'] = -300
        a._user = None
        rv = a.get_user_by_session()
        self.assertEqual(rv['user_id'], user_id)
        self.assertNotEqual(rv['token'], token)

        # Now let's force token to be invalid.
        s.config['token_max_age'] = -300
        a._user = None
        rv = a.get_user_by_session()
        self.assertEqual(rv, None)

    def test_get_user_by_password(self):
        app = webapp2.WSGIApplication(config={
            'webapp2_extras.sessions': {
                'secret_key': 'foo',
            }
        })
        req = webapp2.Request.blank('/')
        req.app = app
        s = auth.get_store(app=app)
        a = auth.get_auth(request=req)
        session_store = sessions.get_store(request=req)

        m = models.User
        success, user = m.create_user(auth_id='auth_id',
                                      password_raw='password')

        user_id = user.key.id()
        # Lets test the cookie max_age when we use remember=True or False.
        rv = a.get_user_by_password('auth_id', 'password', remember=True)
        self.assertEqual(rv['user_id'], user_id)
        self.assertEqual(session_store.sessions['auth'].session_args['max_age'],
                         86400 * 7 * 3)

        # Now remember=False.
        rv = a.get_user_by_password('auth_id', 'password')
        self.assertEqual(rv['user_id'], user_id)
        self.assertEqual(session_store.sessions['auth'].session_args['max_age'],
                         None)

        # User was set so getting it from session will return the same one.
        rv = a.get_user_by_session()
        self.assertEqual(rv['user_id'], user_id)

        # Now try a failed password submission: user will be unset.
        rv = a.get_user_by_password('auth_id', 'password_2', silent=True)
        self.assertTrue(rv is None)

        # And getting by session will no longer work.
        rv = a.get_user_by_session()
        self.assertTrue(rv is None)

    def test_validate_password(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        s = auth.get_store(app=app)

        m = models.User
        success, user = m.create_user(auth_id='auth_id',
                                      password_raw='foo')

        u = s.validate_password('auth_id', 'foo')
        self.assertEqual(u, s.user_to_dict(user))
        self.assertRaises(auth.InvalidPasswordError,
                          s.validate_password, 'auth_id', 'bar')
        self.assertRaises(auth.InvalidAuthIdError,
                          s.validate_password, 'auth_id_2', 'foo')

    def test_validate_token(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        s = auth.get_store(app=app)

        rv = s.validate_token('auth_id', 'token')
        self.assertEqual(rv, (None, None))

        # Expired timestamp.
        rv = s.validate_token('auth_id', 'token', -300)
        self.assertEqual(rv, (None, None))

        m = models.User
        success, user = m.create_user(auth_id='auth_id',
                                      password_raw='foo')

        user_id = user.key.id()
        token = m.create_auth_token(user_id)
        rv = s.validate_token(user_id, token)
        self.assertEqual(rv, (s.user_to_dict(user), token))
        # Token must still be there.
        self.assertTrue(self._check_token(user_id, token))

        # Expired timestamp.
        token = m.create_auth_token(user_id)
        rv = s.validate_token(user_id, token, -300)
        self.assertEqual(rv, (None, None))
        # Token must have been deleted.
        self.assertFalse(self._check_token(user_id, token))

        # Force expiration.
        token = m.create_auth_token(user_id)
        s.config['token_max_age'] = -300
        rv = s.validate_token(user_id, token)
        self.assertEqual(rv, (None, None))
        # Token must have been deleted.
        self.assertFalse(self._check_token(user_id, token))

        # Revert expiration, force renewal.
        token = m.create_auth_token(user_id)
        s.config['token_max_age'] = 86400 * 7 * 3
        s.config['token_new_age'] = -300
        rv = s.validate_token(user_id, token)
        self.assertEqual(rv, (s.user_to_dict(user), None))
        # Token must have been deleted.
        self.assertFalse(self._check_token(user_id, token))

    def test_set_auth_store(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        store = auth.AuthStore(app)

        self.assertEqual(len(app.registry), 0)
        auth.set_store(store, app=app)
        self.assertEqual(len(app.registry), 1)
        s = auth.get_store(app=app)
        self.assertTrue(isinstance(s, auth.AuthStore))

    def test_get_auth_store(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        self.assertEqual(len(app.registry), 0)
        s = auth.get_store(app=app)
        self.assertEqual(len(app.registry), 1)
        self.assertTrue(isinstance(s, auth.AuthStore))

    def test_set_auth(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        a = auth.Auth(req)

        self.assertEqual(len(req.registry), 0)
        auth.set_auth(a, request=req)
        self.assertEqual(len(req.registry), 1)
        a = auth.get_auth(request=req)
        self.assertTrue(isinstance(a, auth.Auth))

    def test_get_auth(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        self.assertEqual(len(req.registry), 0)
        a = auth.get_auth(request=req)
        self.assertEqual(len(req.registry), 1)
        self.assertTrue(isinstance(a, auth.Auth))

    '''
    def test_set_callables(self):
        app = webapp2.WSGIApplication()
        req = webapp2.Request.blank('/')
        req.app = app
        s = auth.get_store(app=app)

        def validate_password(store, auth_id, password):
            self.assertTrue(store is s)
            self.assertEqual(auth_id, 'auth_id')
            self.assertEqual(password, 'password')
            return 'validate_password'

        def validate_token(store, auth_id, token, token_ts=None):
            self.assertTrue(store is s)
            self.assertEqual(auth_id, 'auth_id')
            self.assertEqual(token, 'token')
            self.assertEqual(token_ts, 'token_ts')
            return 'validate_token'

        s.set_password_validator(validate_password)
        rv = s.validate_password('auth_id', 'password')
        self.assertEqual(rv, 'validate_password')

        s.set_token_validator(validate_token)
        rv = s.validate_token('auth_id', 'token', 'token_ts')
        self.assertEqual(rv, 'validate_token')
    '''

    def test_extended_user(self):
        class MyUser(models.User):
            newsletter = model.BooleanProperty()
            age = model.IntegerProperty()

        auth_id = 'own:username'
        success, info = MyUser.create_user(auth_id, newsletter=True, age=22)
        self.assertTrue(success)

        app = webapp2.WSGIApplication(config={
            'webapp2_extras.auth': {
                'user_model': MyUser,
            }
        })
        s = auth.get_store(app=app)
        user = s.user_model.get_by_auth_id(auth_id)
        self.assertEqual(info, user)
        self.assertEqual(user.age, 22)
        self.assertTrue(user.newsletter is True)


if __name__ == '__main__':
    test_base.main()