# Copyright 2012 Twitter, Inc. and others.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'test_helper'

class UsersTest < ActiveSupport::TestCase
  setup do
    # mock out LDAP with some test users
    mock_ldap do
      user 'priv_user', 'a', :priv => true
      user 'access_user', 'b'
      user 'noaccess_user', 'c', :access => false

      group 'priv', :priv => true do
        user 'priv_grp_user', 'd'
        user 'priv_grp_user_2', 'e'
      end

      group 'priv2', :priv => true do
        user 'priv_grp_user_3', 'f'
      end

      group 'access' do
        user 'access_grp_user', 'g'
      end

      group 'noaccess', :access => false do
        user 'noaccess_user', 'h'
      end
    end
  end

  test "username required and unique" do
    user = build :user, :username => nil
    assert user.invalid?, "User with no username was valid"

    user = build :user, :username => ' '
    assert user.invalid?, "User with blank username was valid"

    user = build :user, :username => 'foo'
    assert user.valid?, "User with OK username was invalid"
    user.save!

    user = build :user, :username => 'foo'
    assert user.invalid? "User with duplate username was valid"
  end

  test "password confirmation" do
    user = build :user, :password => 'hi', :password_confirmation => 'bye'
    assert user.invalid?, "User with incorrect password confirmation was valid"

    user = build :user, :password => 'hi', :password_confirmation => 'hi'
    assert user.valid?, "User with correct password confirmation was invalid"
  end

  # asserts that attempting to assign <attr> the value <val> as part of a
  # mass-assignment fails.
  def assert_protected attr, val
    user = build :user
    old_attr = user[attr]
    user.update_attributes attr => val
    assert_equal old_attr, user[attr]
  end

  test "protected attributes" do
    user = build :user

    assert_protected :username, 'new_username'
    assert_protected :password_digest, 'new_digest'
    assert_protected :privileged, true
  end

  test "key autogenerated" do
    user = create :user
    assert_not_nil user.key
    assert_equal User::KEY_LENGTH, user.key.length
  end

  test "auth new user" do
    # test that we create new users the first time they auth
    user = User.auth 'priv_user', 'a'
    assert_equal 'priv_user', user.username
    assert_equal 'priv_user@twitter.com', user.email
    assert_equal 'Priv_user', user.name
    assert_equal true, user.privileged
  end

  test "auth existing user" do
    # test that we return an existing User instance if one exists
    user = create :user, :username => 'access_user'
    authed_user = User.auth 'access_user', 'b'
    assert_equal user, authed_user
    assert_equal false, authed_user.privileged
  end

  test "LDAP ACLs" do
    assert_privileged 'priv_user',       'a'
    assert_access     'access_user',     'b'
    assert_no_access  'noaccess_user',   'c'
    assert_privileged 'priv_grp_user',   'd'
    assert_privileged 'priv_grp_user_2', 'e'
    assert_privileged 'priv_grp_user_3', 'f'
    assert_access     'access_grp_user', 'g'
    assert_no_access  'noaccess_user',   'h'

    # wrong password
    assert_no_access 'priv_user', 'wrong'

    # non-existant
    assert_no_access 'nonexistant', 'pass'
  end

  test "can't use password-based login when set up to use LDAP" do
    passuser = create :password_user, :username => 'passuser', :password => 'mypass'
    assert_no_access 'passuser', 'mypass'
  end

  test "Can't set a non-blank password to blank" do
    passuser = create :password_user, :password => 'foopass'
    passuser.password = ' '
    passuser.password_confirmation = ' '
    passuser.save
    assert_equal passuser.password, 'foopass'
  end

  test "password login" do
    with_consts :AUTH_CONFIG => {:type => :password} do
      passuser_priv = create :password_user, :username => 'passuser_priv',
                                             :password => 'foopass',
                                             :privileged => true

      passuser_unpriv = create :password_user, :username => 'passuser_unpriv',
                                               :password => 'barpass'

      # correct logins
      assert_privileged 'passuser_priv',   'foopass'
      assert_access     'passuser_unpriv', 'barpass'

      # wrong password
      assert_no_access 'passuser_priv', 'nope'

      # no such user
      assert_no_access 'nobody', 'password'

      # with password auth enabled, shouldn't be able to log in to ldap accounts
      assert_no_access 'priv_user', 'a'
      assert_no_access 'priv_user', nil

      # basic tests to make sure we're not storing passwords in cleartext
      assert !passuser_priv.password.to_s.include?('foopass')
      assert !passuser_priv.password_digest.to_s.include?('foopass')
    end
  end


  # Authenticates using the given credentials and asserts the authentication
  # was successful and returned a privileged user.
  def assert_privileged username, password
    user = User.auth username, password
    assert_not_nil user, "Authentication of privileged user #{user} failed"
    assert_equal true, user.privileged, "Expected user #{user} to be privileged"
  end

  # Authenticates using the given credentials and asserts the authentication
  # was successful but returned an unprivileged user.
  def assert_access username, password
    user = User.auth username, password
    assert_not_nil user, "Authentication of unprivileged user #{user} failed"
    assert_equal false, user.privileged, "Expected user #{user} to be unprivileged"
  end

  # Attempts to authenticate using the given credentials and asserts the
  # authentication failed.
  def assert_no_access username, password
    user = User.auth username, password
    assert_nil user, "Authentication of user #{user} suceeded, expected to fail"
  end
end