Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

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

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

#!/usr/bin/env python 

# -*- coding: utf-8 -*- 

 

############################################################################### 

#  Copyright 2013 Kitware Inc. 

# 

#  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. 

############################################################################### 

 

import cherrypy 

import hashlib 

import re 

 

from girder.utility import config 

from .model_base import Model, ValidationException 

from .token import genToken 

 

 

class Password(Model): 

    """ 

    This model deals with managing user passwords. 

    """ 

    def initialize(self): 

        self.name = 'password' 

 

    def _digest(self, alg, password, salt=None): 

        """ 

        Helper method to perform the password digest. 

 

        :param alg: The hash algorithm to use. 

        :type alg: str - 'sha512' | 'bcrypt' 

        :param password: The password to digest. 

        :type password: str 

        :param salt: The salt to use. In the case of bcrypt, 

                     when storing the password, pass None; 

                     when testing the password, pass the hashed value. 

        :type salt: None or str 

        :returns: The hashed value as a string. 

        """ 

        cur_config = config.getConfig() 

        if alg == 'sha512': 

            return hashlib.sha512(password + salt).hexdigest() 

        elif alg == 'bcrypt': 

            try: 

                import bcrypt 

            except ImportError: 

                raise Exception('Bcrypt module is not installed. ' 

                                'See local.auth.cfg.') 

 

            if salt is None: 

                rounds = cur_config['auth']['bcrypt_rounds'] 

                assert type(rounds) is int 

                return bcrypt.hashpw(password, bcrypt.gensalt(rounds)) 

            else: 

                if type(salt) is unicode: 

                    salt = salt.encode('utf-8') 

                return bcrypt.hashpw(password, salt) 

        else: 

            raise Exception('Unsupported hash algorithm: %s' % alg) 

 

    def validate(self, doc): 

        if not doc.get('_id', ''): 

            # Internal error, this should not happen 

            raise Exception('Attempting to store empty password.') 

 

        return doc 

 

    def authenticate(self, user, password): 

        """ 

        Authenticate a user. 

 

        :param user: The user document. 

        :type user: dict 

        :param password: The attempted password. 

        :type password: str 

        :returns: Whether authentication succeeded (bool). 

        """ 

        hash = self._digest(salt=user['salt'], alg=user['hashAlg'], 

                            password=password) 

 

        if user['hashAlg'] == 'bcrypt': 

            return hash == user['salt'] 

        else: 

            return self.load(hash, False) is not None 

 

    def encryptAndStore(self, password): 

        """ 

        Encrypt and store the given password. The exact internal details and 

        mechanisms used for storage are abstracted away, but the guarantee is 

        made that once this method is called on a password and the returned salt 

        and algorithm are stored with the user document, calling 

        Password.authenticate() with that user document and the same password 

        will return True. 

 

        :param password: The password to encrypt and store. 

        :type password: str 

        :returns: {tuple} (salt, hashAlg) The salt to store with the 

                  user document and the algorithm used for secure 

                  storage. Both should be stored in the corresponding 

                  user document as 'salt' and 'hashAlg' respectively. 

        """ 

        cur_config = config.getConfig() 

 

        # Normally this would go in validate() but password is a special case. 

        if not re.match(cur_config['users']['password_regex'], password): 

            raise ValidationException( 

                cur_config['users']['password_description'], 'password') 

 

        alg = cherrypy.config['auth']['hash_alg'] 

        if alg == 'bcrypt': 

            """ 

            With bcrypt, we actually need the one-to-one correspondence of 

            hashed password to user, so we store the hash as the salt entry in 

            the user table. 

            """ 

            salt = self._digest(alg=alg, password=password) 

        else: 

            """ 

            With other hashing algorithms, we store the salt with the user 

            and store the hashed value in a separate table with no 

            correspondence to the user. 

            """ 

            salt = genToken() 

            hash = self._digest(salt=salt, alg=alg, password=password) 

            self.save({'_id': hash}) 

 

        return (salt, alg)