Class: LastPass::Fetcher

Inherits:
Object
  • Object
show all
Defined in:
lib/lastpass/fetcher.rb

Class Method Summary collapse

Class Method Details

.create_session(parsed_response, key_iteration_count) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/lastpass/fetcher.rb', line 68

def self.create_session parsed_response, key_iteration_count
    ok = parsed_response["ok"]
    if ok.is_a? Hash
        session_id = ok["sessionid"]
        if session_id.is_a? String
            return Session.new session_id, key_iteration_count
        end
    end

    nil
end

.decode_blob(blob) ⇒ Object



102
103
104
105
# File 'lib/lastpass/fetcher.rb', line 102

def self.decode_blob blob
    # TODO: Check for invalid base64
    Base64.decode64 blob
end

.fetch(session, web_client = HTTParty) ⇒ Object

Raises:



11
12
13
14
15
16
17
18
19
# File 'lib/lastpass/fetcher.rb', line 11

def self.fetch session, web_client = HTTParty
    response = web_client.get "https://lastpass.com/getaccts.php?mobile=1&b64=1&hash=0.0&hasplugin=3.0.23&requestsrc=android",
                              format: :plain,
                              cookies: {"PHPSESSID" => URI.encode(session.id)}

    raise NetworkError unless response.response.is_a? Net::HTTPOK

    Blob.new decode_blob(response.parsed_response), session.key_iteration_count
end

.login(username, password, multifactor_password = nil) ⇒ Object



6
7
8
9
# File 'lib/lastpass/fetcher.rb', line 6

def self. username, password, multifactor_password = nil
    key_iteration_count = request_iteration_count username
     username, password, key_iteration_count, multifactor_password
end

.login_error(parsed_response) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/lastpass/fetcher.rb', line 80

def self. parsed_response
    error = (parsed_response["response"] || {})["error"]
    return UnknownResponseSchemaError unless error.is_a? Hash

    exceptions = {
        "unknownemail" => LastPassUnknownUsernameError,
        "unknownpassword" => LastPassInvalidPasswordError,
        "googleauthrequired" => LastPassIncorrectGoogleAuthenticatorCodeError,
        "googleauthfailed" => LastPassIncorrectGoogleAuthenticatorCodeError,
        "yubikeyrestricted" => LastPassIncorrectYubikeyPasswordError,
    }

    cause = error["cause"]
    message = error["message"]

    if cause
        (exceptions[cause] || LastPassUnknownError).new message || cause
    else
        InvalidResponseError.new message
    end
end

.make_hash(username, password, key_iteration_count) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/lastpass/fetcher.rb', line 121

def self.make_hash username, password, key_iteration_count
    if key_iteration_count == 1
        Digest::SHA256.hexdigest Digest.hexencode(make_key(username, password, 1)) + password
    else
        PBKDF2
            .new(password: make_key(username, password, key_iteration_count),
                 salt: password,
                 iterations: 1,
                 key_length: 32)
            .hex_string
    end
end

.make_key(username, password, key_iteration_count) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/lastpass/fetcher.rb', line 107

def self.make_key username, password, key_iteration_count
    if key_iteration_count == 1
        Digest::SHA256.digest username + password
    else
        PBKDF2
            .new(password: password,
                 salt: username,
                 iterations: key_iteration_count,
                 key_length: 32)
            .bin_string
            .force_encoding "BINARY"
    end
end

.request_iteration_count(username, web_client = HTTParty) ⇒ Object

Raises:



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/lastpass/fetcher.rb', line 21

def self.request_iteration_count username, web_client = HTTParty
    response = web_client.post "https://lastpass.com/iterations.php",
                               query: {email: username}

    raise NetworkError unless response.response.is_a? Net::HTTPOK

    begin
        count = Integer response.parsed_response
    rescue ArgumentError
        raise InvalidResponseError, "Key iteration count is invalid"
    end

    raise InvalidResponseError, "Key iteration count is not positive" unless count > 0

    count
end

.request_login(username, password, key_iteration_count, multifactor_password = nil, web_client = HTTParty) ⇒ Object

Raises:



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
# File 'lib/lastpass/fetcher.rb', line 38

def self. username,
                       password,
                       key_iteration_count,
                       multifactor_password = nil,
                       web_client = HTTParty

    body = {
        method: "mobile",
        web: 1,
        xml: 1,
        username: username,
        hash: make_hash(username, password, key_iteration_count),
        iterations: key_iteration_count
    }

    body[:otp] = multifactor_password if multifactor_password

    response = web_client.post "https://lastpass.com/login.php",
                               format: :xml,
                               body: body

    raise NetworkError unless response.response.is_a? Net::HTTPOK

    parsed_response = response.parsed_response
    raise InvalidResponseError unless parsed_response.is_a? Hash

    create_session parsed_response, key_iteration_count or
        raise  parsed_response
end