lastpass-api 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +229 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/lastpass-api.gemspec +31 -0
- data/lib/lastpass-api.rb +42 -0
- data/lib/lastpass-api/account.rb +108 -0
- data/lib/lastpass-api/accounts.rb +29 -0
- data/lib/lastpass-api/cli.rb +209 -0
- data/lib/lastpass-api/client.rb +52 -0
- data/lib/lastpass-api/collection.rb +27 -0
- data/lib/lastpass-api/group.rb +69 -0
- data/lib/lastpass-api/groups.rb +30 -0
- data/lib/lastpass-api/parser.rb +49 -0
- data/lib/lastpass-api/utils.rb +34 -0
- data/lib/lastpass-api/version.rb +3 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2425a8ba66083cd5a82dcf4c1f6ac2181370bbaf
|
4
|
+
data.tar.gz: ca05d47f5115de2bc195c2e57f969f89cb568d80
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bcff24dd438a9b02075e44ecbf558dd81a4c4e1dfe25aaffa0d0a57a8ef785d1745c3f76f59ce658af9d2ddaaa9440ed3ff1ba10557d5e018bb590214ec6bc87
|
7
|
+
data.tar.gz: b893abc29437a51ded0ed07a65d812da6a82c83db57acf7142c81f5325b057ef69cedd87dcb30f75acc0e07bcca6e915b4ae15295af89d00ce62f1c820677434
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at eric@entretechno.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Entrepreneurial Technologies
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
# Lastpass API
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/lastpass-api)
|
4
|
+

|
5
|
+
[](http://inch-ci.org/github/entretechno/lastpass-api)
|
6
|
+
|
7
|
+
Read/Write access to the online [LastPass](https://www.lastpass.com) vault using LastPass CLI to create, read, and update account information and credentials.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
```bash
|
12
|
+
gem install lastpass-api
|
13
|
+
```
|
14
|
+
|
15
|
+
This gem depends on [lpass](https://github.com/lastpass/lastpass-cli), which requires the `lpass` executable to be installed and in the PATH. Try running one of these (depending on your OS) to install `lpass`:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
# Ubuntu (may have to build manually to install verion v1)
|
19
|
+
sudo apt-get install openssl libcurl4-openssl-dev libxml2 libssl-dev libxml2-dev pinentry-curses xclip cmake
|
20
|
+
sudo apt-get install lastpass-cli
|
21
|
+
|
22
|
+
# Homebrew (OS X)
|
23
|
+
brew update && brew install lastpass-cli
|
24
|
+
|
25
|
+
# MacPorts (OS X)
|
26
|
+
sudo port selfupdate && sudo port install lastpass-cli
|
27
|
+
|
28
|
+
# Debian (may have to build manually to install verion v1)
|
29
|
+
sudo apt-get install openssl libcurl3 libxml2 libssl-dev libxml2-dev libcurl4-openssl-dev pinentry-curses xclip
|
30
|
+
sudo apt-get install lastpass-cli
|
31
|
+
|
32
|
+
# Fedora
|
33
|
+
sudo dnf install lastpass-cli
|
34
|
+
|
35
|
+
# Redhat/Centos
|
36
|
+
sudo yum install openssl libcurl libxml2 pinentry xclip openssl-devel libxml2-devel libcurl-devel
|
37
|
+
sudo yum install lastpass-cli
|
38
|
+
|
39
|
+
# Gentoo
|
40
|
+
sudo emerge lastpass-cli
|
41
|
+
|
42
|
+
# FreeBSD
|
43
|
+
sudo pkg install security/lastpass-cli
|
44
|
+
sudo make -C /usr/ports/security/lastpass-cli all install clean
|
45
|
+
|
46
|
+
# Cygwin
|
47
|
+
apt-cyg install wget make cmake gcc-core gcc-g++ openssl-devel libcurl-devel libxml2-devel libiconv-devel cygutils-extra
|
48
|
+
```
|
49
|
+
|
50
|
+
Instructions for building manually: https://github.com/lastpass/lastpass-cli#building
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'lastpass-api'
|
56
|
+
@lastpass = Lastpass::Client.new
|
57
|
+
```
|
58
|
+
|
59
|
+
### Login
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
@lastpass.login( email: 'user@example.com', password: 'secret' )
|
63
|
+
puts @lastpass.logged_in?
|
64
|
+
```
|
65
|
+
|
66
|
+
### Accounts
|
67
|
+
|
68
|
+
#### Create account credentials
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# Create an optional group to place account into
|
72
|
+
@lastpass.groups.create( name: 'MyGroup' )
|
73
|
+
|
74
|
+
# Create account
|
75
|
+
account = @lastpass.accounts.create(
|
76
|
+
name: 'MyAccount',
|
77
|
+
username: 'root',
|
78
|
+
password: 'pass',
|
79
|
+
url: 'http://www.example.com',
|
80
|
+
notes: 'This is my note.',
|
81
|
+
group: 'MyGroup'
|
82
|
+
)
|
83
|
+
puts account.id
|
84
|
+
```
|
85
|
+
|
86
|
+
#### Find and read credentials
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# Find a specific account by name
|
90
|
+
account = @lastpass.accounts.find( 'MyAccount', with_password: true )
|
91
|
+
|
92
|
+
# Find a specific account by ID
|
93
|
+
account = @lastpass.accounts.find( 1234, with_password: true )
|
94
|
+
|
95
|
+
puts account.to_h
|
96
|
+
# => { id: '1234', name: 'MyAccount', username: 'root', password: 'pass', url: 'http://www.example.com', notes: 'This is my note.', group: 'MyGroup' }
|
97
|
+
|
98
|
+
# Find all accounts that match string (or regex)
|
99
|
+
accounts = @lastpass.accounts.find_all( 'MyAcc' )
|
100
|
+
puts accounts.count
|
101
|
+
puts accounts.first.to_h
|
102
|
+
|
103
|
+
# Fetch all accounts - same as find_all( '.*' )
|
104
|
+
@lastpass.accounts.find_all
|
105
|
+
```
|
106
|
+
|
107
|
+
#### Update account
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# Update using instance variables
|
111
|
+
account = @lastpass.accounts.find( 'MyAccount' )
|
112
|
+
account.name = 'MyAccount EDIT'
|
113
|
+
account.username = 'root EDIT'
|
114
|
+
account.password = 'pass EDIT'
|
115
|
+
account.url = 'http://www.exampleEDIT.com'
|
116
|
+
account.notes = 'This is my notes. EDIT'
|
117
|
+
account.save
|
118
|
+
|
119
|
+
# Update using the update method
|
120
|
+
account = @lastpass.accounts.find( 'MyAccount' )
|
121
|
+
account.update(
|
122
|
+
name: 'MyAccount EDIT',
|
123
|
+
username: 'root EDIT',
|
124
|
+
password: 'pass EDIT',
|
125
|
+
url: 'http://www.exampleEDIT.com',
|
126
|
+
notes: 'This is my note. EDIT'
|
127
|
+
)
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Delete account
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
account = @lastpass.accounts.find( 1234 )
|
134
|
+
account.delete
|
135
|
+
```
|
136
|
+
|
137
|
+
### Groups (Folders)
|
138
|
+
|
139
|
+
#### Create group
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
group = @lastpass.groups.create( name: 'Group1' )
|
143
|
+
puts group.id
|
144
|
+
```
|
145
|
+
|
146
|
+
#### Find group
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
# Find a specific group by name
|
150
|
+
group = @lastpass.groups.find( 'Group1' )
|
151
|
+
|
152
|
+
# Find a specific group by ID
|
153
|
+
group = @lastpass.groups.find( 1234 )
|
154
|
+
|
155
|
+
puts group.to_h
|
156
|
+
# => { id: '1234', name: 'Group1' }
|
157
|
+
|
158
|
+
# Find all groups that match string (or regex)
|
159
|
+
groups = @lastpass.groups.find_all( 'Gro' )
|
160
|
+
puts groups.count
|
161
|
+
puts groups.first.to_h
|
162
|
+
|
163
|
+
# Fetch all groups - same as find_all( '.*' )
|
164
|
+
@lastpass.groups.find_all
|
165
|
+
```
|
166
|
+
|
167
|
+
#### Update group (rename a group)
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
# Update using instance variables
|
171
|
+
group = @lastpass.groups.find( 'Group1' )
|
172
|
+
group.name = 'Group1 EDIT'
|
173
|
+
group.save
|
174
|
+
|
175
|
+
# Update using the update method
|
176
|
+
group = @lastpass.groups.find( 'Group1' )
|
177
|
+
group.update( name: 'Group1 EDIT' )
|
178
|
+
```
|
179
|
+
|
180
|
+
#### Delete group
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
group = @lastpass.groups.find( 1234 )
|
184
|
+
group.delete
|
185
|
+
```
|
186
|
+
|
187
|
+
### Logout
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
@lastpass.logout
|
191
|
+
puts @lastpass.logged_out?
|
192
|
+
```
|
193
|
+
|
194
|
+
### Verbose
|
195
|
+
|
196
|
+
Turning on verbose will show much more output. This is good for debugging. It will output any commands that are executed with `lpass`.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
@lastpass = Lastpass::Client.new( verbose: true )
|
200
|
+
# or
|
201
|
+
Lastpass.verbose = true
|
202
|
+
```
|
203
|
+
|
204
|
+
### Directly interacting with CLI
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
Lastpass::Cli.login( username, password:, trust: false, plaintext_key: false, force: false )
|
208
|
+
Lastpass::Cli.logout( force: false )
|
209
|
+
Lastpass::Cli.show( account, clip: false, expand_multi: false, all: false, basic_regexp: false, id: false )
|
210
|
+
Lastpass::Cli.ls( group = nil, long: false, m: false, u: false )
|
211
|
+
Lastpass::Cli.add( name, username: nil, password: nil, url: nil, notes: nil, group: nil )
|
212
|
+
Lastpass::Cli.add_group( name )
|
213
|
+
Lastpass::Cli.edit( id, name: nil, username: nil, password: nil, url: nil, notes: nil, group: nil )
|
214
|
+
Lastpass::Cli.edit_group( id, name: )
|
215
|
+
Lastpass::Cli.rm( id )
|
216
|
+
Lastpass::Cli.status( quiet: false )
|
217
|
+
Lastpass::Cli.sync
|
218
|
+
Lastpass::Cli.export
|
219
|
+
Lastpass::Cli.import( csv_filename )
|
220
|
+
Lastpass::Cli.version
|
221
|
+
```
|
222
|
+
|
223
|
+
## Contributing
|
224
|
+
|
225
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/entretechno/lastpass-api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
226
|
+
|
227
|
+
## License
|
228
|
+
|
229
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path( '../lib', __FILE__ )
|
3
|
+
$LOAD_PATH.unshift( lib ) unless $LOAD_PATH.include?( lib )
|
4
|
+
require 'lastpass-api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'lastpass-api'
|
8
|
+
spec.version = Lastpass::VERSION
|
9
|
+
spec.authors = ['Eric Terry']
|
10
|
+
spec.email = ['eterry1388@aol.com']
|
11
|
+
|
12
|
+
spec.summary = 'Read/Write access to the online LastPass vault using LastPass CLI'
|
13
|
+
spec.description = 'Full access to the LastPass vault to create, read, and update account information and credentials.'
|
14
|
+
spec.homepage = 'http://www.entretechno.com'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split( "\x0" ).reject do |file|
|
18
|
+
file.match( %r{^(test|spec|features)/} )
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep( %r{^exe/} ) { |file| File.basename( file ) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'colorize'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
|
+
spec.add_development_dependency 'byebug'
|
31
|
+
end
|
data/lib/lastpass-api.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'lastpass-api/utils'
|
2
|
+
require 'lastpass-api/collection'
|
3
|
+
require 'lastpass-api/cli'
|
4
|
+
require 'lastpass-api/client'
|
5
|
+
require 'lastpass-api/account'
|
6
|
+
require 'lastpass-api/accounts'
|
7
|
+
require 'lastpass-api/group'
|
8
|
+
require 'lastpass-api/groups'
|
9
|
+
require 'lastpass-api/parser'
|
10
|
+
|
11
|
+
# Read/Write API client for LastPass using the LastPass CLI. I am not
|
12
|
+
# affiliated with LastPass.
|
13
|
+
#
|
14
|
+
# @author {mailto:eterry1388@aol.com Eric Terry}
|
15
|
+
# @note Tested with LastPass CLI v1.1.2
|
16
|
+
# @note This gem currently can only login with one account at a time!
|
17
|
+
module Lastpass
|
18
|
+
@@verbose = false
|
19
|
+
|
20
|
+
def self.verbose
|
21
|
+
@@verbose
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.verbose=( verbose )
|
25
|
+
@@verbose = verbose
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.check_lpass_in_path
|
29
|
+
error_message = 'Cannot find the "lpass" executable in the path!'
|
30
|
+
begin
|
31
|
+
raise error_message if Utils.cmd( 'which lpass' ) == ''
|
32
|
+
rescue
|
33
|
+
raise error_message
|
34
|
+
end
|
35
|
+
version = Utils.cmd( 'lpass --version' )
|
36
|
+
unless version.include? 'v1.'
|
37
|
+
raise "The LastPass CLI you have installed [#{version&.chomp}] is not supported. Please install LastPass CLI v1"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
check_lpass_in_path # Check upon requiring the gem
|
42
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Account
|
3
|
+
attr_reader :id, :group # TODO: Make group editable eventually
|
4
|
+
attr_accessor :name, :username, :password, :url, :notes
|
5
|
+
|
6
|
+
def initialize( params )
|
7
|
+
params_to_account( params )
|
8
|
+
end
|
9
|
+
|
10
|
+
def update( params )
|
11
|
+
deleted! if @deleted
|
12
|
+
params.delete( :id ) # Prevent overwriting ID
|
13
|
+
params.delete( :group ) # Prevent overwriting group
|
14
|
+
params_to_account( params )
|
15
|
+
save
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: This does not support changing groups yet!
|
19
|
+
def save
|
20
|
+
deleted! if @deleted
|
21
|
+
# If there is an ID, update that entry
|
22
|
+
if @id
|
23
|
+
Cli.edit( @id,
|
24
|
+
name: @name,
|
25
|
+
username: @username,
|
26
|
+
password: @password,
|
27
|
+
url: @url,
|
28
|
+
notes: @notes,
|
29
|
+
group: @group
|
30
|
+
)
|
31
|
+
else # If no ID, that means this is a new entry
|
32
|
+
Cli.add( @name,
|
33
|
+
username: @username,
|
34
|
+
password: @password,
|
35
|
+
url: @url,
|
36
|
+
notes: @notes,
|
37
|
+
group: @group
|
38
|
+
)
|
39
|
+
set_id_after_save
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete
|
45
|
+
Cli.rm( @id )
|
46
|
+
@deleted = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_hash
|
50
|
+
params = {}
|
51
|
+
params[:id] = @id if @id
|
52
|
+
params[:name] = @name if @name
|
53
|
+
params[:username] = @username if @username
|
54
|
+
params[:password] = @password if @password
|
55
|
+
params[:url] = @url if @url
|
56
|
+
params[:notes] = @notes if @notes
|
57
|
+
params[:group] = @group if @group
|
58
|
+
params
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :to_h, :to_hash
|
62
|
+
|
63
|
+
# Hide instance variables and values
|
64
|
+
def inspect
|
65
|
+
original_inspect = super
|
66
|
+
original_inspect.split( ' ' ).first << '>'
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def params_to_account( params )
|
72
|
+
set_group_and_name( params[:name] ) # Infer group from the name
|
73
|
+
@group = params[:group] if params[:group] # Overwrite group if explicitly set
|
74
|
+
@id = params[:id] if params[:id]
|
75
|
+
@username = params[:username] if params[:username]
|
76
|
+
@password = params[:password] if params[:password]
|
77
|
+
@url = params[:url] if params[:url]
|
78
|
+
@notes = params[:notes] if params[:notes]
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_group_and_name( string )
|
82
|
+
if string
|
83
|
+
split_name = string.split( '/' )
|
84
|
+
if split_name.count == 1
|
85
|
+
@name = split_name.first
|
86
|
+
else
|
87
|
+
@name = split_name.last
|
88
|
+
@group = split_name.first
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_id_after_save
|
94
|
+
show_name = ''
|
95
|
+
show_name << "#{@group}/" if @group
|
96
|
+
show_name << @name
|
97
|
+
response = Cli.show( show_name, id: true )
|
98
|
+
if response.nil? || response == '' || response.include?( 'Multiple matches found' )
|
99
|
+
raise "Unable to fetch ID of newly created account! Name may have not been unique. Response: #{response}"
|
100
|
+
end
|
101
|
+
@id = response
|
102
|
+
end
|
103
|
+
|
104
|
+
def deleted!
|
105
|
+
raise "Account [ID:#{@id}] has been deleted!"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Accounts < Collection
|
3
|
+
|
4
|
+
def find( search_text, with_password: false )
|
5
|
+
params = super
|
6
|
+
if params.is_a?( Hash ) && !params[:name]&.end_with?( '/' )
|
7
|
+
Account.new( params )
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_all( search_text = '.*', with_passwords: false )
|
12
|
+
accounts = super.select { |params| !params[:name]&.end_with? '/' }
|
13
|
+
accounts.map do |params|
|
14
|
+
Account.new( params )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create( params )
|
19
|
+
params.delete( :id ) # Prevent overwriting ID
|
20
|
+
Account.new( params ).save
|
21
|
+
end
|
22
|
+
|
23
|
+
# Hide instance variables and values
|
24
|
+
def inspect
|
25
|
+
original_inspect = super
|
26
|
+
original_inspect.split( ' ' ).first << '>'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Lastpass
|
4
|
+
|
5
|
+
# Low-level interaction with LastPass CLI
|
6
|
+
class Cli
|
7
|
+
UPLOAD_QUEUE_PATH = '~/.lpass/upload-queue' # TODO: Make this configurable? Or at least smarter?
|
8
|
+
|
9
|
+
# Login to LastPass, opening up a global session
|
10
|
+
#
|
11
|
+
# @note lpass login [--trust] [--plaintext-key [--force, -f]] [--color=auto|never|always] USERNAME
|
12
|
+
def self.login( username, password:, trust: false, plaintext_key: false, force: false )
|
13
|
+
command = "echo '#{password}' | LPASS_DISABLE_PINENTRY=1 lpass login"
|
14
|
+
command << ' --trust' if trust
|
15
|
+
command << ' --plaintext-key' if plaintext_key
|
16
|
+
command << ' --force' if force
|
17
|
+
command << " '#{escape( username )}'"
|
18
|
+
Utils.cmd command
|
19
|
+
end
|
20
|
+
|
21
|
+
# @note lpass logout [--force, -f] [--color=auto|never|always]
|
22
|
+
def self.logout( force: false )
|
23
|
+
command = 'echo "Y" | lpass logout'
|
24
|
+
command << ' --force' if force
|
25
|
+
Utils.cmd command
|
26
|
+
end
|
27
|
+
|
28
|
+
# @note lpass passwd
|
29
|
+
def self.passwd
|
30
|
+
not_implemented!
|
31
|
+
end
|
32
|
+
|
33
|
+
# @note lpass show [--sync=auto|now|no] [--clip, -c] [--expand-multi, -x] [--all|--username|--password|--url|--notes|--field=FIELD|--id|--name|--attach=ATTACHID] [--basic-regexp, -G|--fixed-strings, -F] [--color=auto|never|always] {UNIQUENAME|UNIQUEID}
|
34
|
+
def self.show( account, clip: false, expand_multi: false, all: false, basic_regexp: false, id: false )
|
35
|
+
sync # Ensure everything is synced up before running!
|
36
|
+
command = 'lpass show'
|
37
|
+
command << ' --clip' if clip
|
38
|
+
command << ' --expand-multi' if expand_multi
|
39
|
+
command << ' --all' if all
|
40
|
+
command << ' --basic-regexp' if basic_regexp
|
41
|
+
command << ' --id' if id
|
42
|
+
command << " '#{escape( account )}'"
|
43
|
+
response = Utils.cmd command
|
44
|
+
# Don't let LastPass know the accounts were accessed as it clogs up the sync!
|
45
|
+
# So clear out sync files if a lot of accounts were accessed at the same time.
|
46
|
+
remove_sync_files if response.length > 400
|
47
|
+
response
|
48
|
+
end
|
49
|
+
|
50
|
+
# @note lpass ls [--sync=auto|now|no] [--long, -l] [-m] [-u] [--color=auto|never|always] [GROUP]
|
51
|
+
def self.ls( group = nil, long: false, m: false, u: false )
|
52
|
+
sync # Ensure everything is synced up before running!
|
53
|
+
command = 'lpass ls'
|
54
|
+
command << ' --long' if long
|
55
|
+
command << ' -m' if m
|
56
|
+
command << ' -u' if u
|
57
|
+
command << " '#{escape( group )}'" if group
|
58
|
+
response = Utils.cmd command
|
59
|
+
# Don't let LastPass know the accounts were accessed as it clogs up the sync!
|
60
|
+
# So clear out sync files if a lot of accounts were accessed at the same time.
|
61
|
+
remove_sync_files if response.length > 400
|
62
|
+
response
|
63
|
+
end
|
64
|
+
|
65
|
+
# @note lpass mv [--color=auto|never|always] {UNIQUENAME|UNIQUEID} GROUP
|
66
|
+
def self.mv
|
67
|
+
not_implemented!
|
68
|
+
end
|
69
|
+
|
70
|
+
# @note lpass add [--sync=auto|now|no] [--non-interactive] [--color=auto|never|always] {--username|--password|--url|--notes|--field=FIELD|--note-type=NOTETYPE} NAME
|
71
|
+
def self.add( name, username: nil, password: nil, url: nil, notes: nil, group: nil )
|
72
|
+
data = {}
|
73
|
+
data[:Username] = escape( username, double: true ) if username
|
74
|
+
data[:Password] = escape( password, double: true ) if password
|
75
|
+
data[:URL] = escape( url, double: true ) if url
|
76
|
+
data[:Notes] = '\n' << escape( notes, double: true ) if notes
|
77
|
+
|
78
|
+
command = 'printf "'
|
79
|
+
command << data.map { |d| d.join( ': ' ) }.join( '\n' )
|
80
|
+
command << '" | lpass add --non-interactive --sync=no \''
|
81
|
+
command << "#{escape( group )}/" if group
|
82
|
+
command << "#{escape( name )}'"
|
83
|
+
response = Utils.cmd command
|
84
|
+
sync
|
85
|
+
response
|
86
|
+
end
|
87
|
+
|
88
|
+
# @note lpass add [--sync=auto|now|no] [--non-interactive] [--color=auto|never|always] {--username|--password|--url|--notes|--field=FIELD|--note-type=NOTETYPE} NAME
|
89
|
+
def self.add_group( name )
|
90
|
+
response = Utils.cmd "printf 'URL: http://group' | lpass add --non-interactive --sync=no '#{escape( name )}/'"
|
91
|
+
sync
|
92
|
+
response
|
93
|
+
end
|
94
|
+
|
95
|
+
# @note lpass edit [--sync=auto|now|no] [--non-interactive] [--color=auto|never|always] {--name|--username|--password|--url|--notes|--field=FIELD} {NAME|UNIQUEID}
|
96
|
+
def self.edit( id, name: nil, username: nil, password: nil, url: nil, notes: nil, group: nil )
|
97
|
+
data = {}
|
98
|
+
name_with_group = ''
|
99
|
+
name_with_group << "#{group}/" if group
|
100
|
+
name_with_group << name if name
|
101
|
+
|
102
|
+
data[:Name] = escape( name_with_group, double: true ) unless name_with_group == ''
|
103
|
+
data[:Username] = escape( username, double: true ) if username
|
104
|
+
data[:Password] = escape( password, double: true ) if password
|
105
|
+
data[:URL] = escape( url, double: true ) if url
|
106
|
+
data[:Notes] = '\n' << escape( notes, double: true ) if notes
|
107
|
+
|
108
|
+
command = 'printf "'
|
109
|
+
command << data.map { |d| d.join( ': ' ) }.join( '\n' )
|
110
|
+
command << '" | lpass edit --non-interactive --sync=no '
|
111
|
+
command << id
|
112
|
+
response = Utils.cmd command
|
113
|
+
sync
|
114
|
+
response
|
115
|
+
end
|
116
|
+
|
117
|
+
# @note lpass edit [--sync=auto|now|no] [--non-interactive] [--color=auto|never|always] {--name|--username|--password|--url|--notes|--field=FIELD} {NAME|UNIQUEID}
|
118
|
+
def self.edit_group( id, name: )
|
119
|
+
command = 'printf "'
|
120
|
+
command << "Name: #{escape( name, double: true )}/"
|
121
|
+
command << '" | lpass edit --non-interactive --sync=no '
|
122
|
+
command << id
|
123
|
+
response = Utils.cmd command
|
124
|
+
sync
|
125
|
+
response
|
126
|
+
end
|
127
|
+
|
128
|
+
# @note lpass generate [--sync=auto|now|no] [--clip, -c] [--username=USERNAME] [--url=URL] [--no-symbols] {NAME|UNIQUEID} LENGTH
|
129
|
+
def self.generate
|
130
|
+
not_implemented! # TODO
|
131
|
+
end
|
132
|
+
|
133
|
+
# @note lpass duplicate [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID}
|
134
|
+
def self.duplicate
|
135
|
+
not_implemented!
|
136
|
+
end
|
137
|
+
|
138
|
+
# @note lpass rm [--sync=auto|now|no] [--color=auto|never|always] {UNIQUENAME|UNIQUEID}
|
139
|
+
def self.rm( id )
|
140
|
+
response = Utils.cmd "lpass rm --sync=no #{id}"
|
141
|
+
sync
|
142
|
+
response
|
143
|
+
end
|
144
|
+
|
145
|
+
# @note lpass status [--quiet, -q] [--color=auto|never|always]
|
146
|
+
def self.status( quiet: false )
|
147
|
+
command = 'lpass status'
|
148
|
+
command << ' --quiet' if quiet
|
149
|
+
Utils.cmd command
|
150
|
+
end
|
151
|
+
|
152
|
+
# @note This is a buggy function of the lpass executable. May not be super reliable.
|
153
|
+
# @note lpass sync [--background, -b] [--color=auto|never|always]
|
154
|
+
def self.sync
|
155
|
+
sleep 1 # Allow file IO before attempting sync
|
156
|
+
Utils.cmd 'lpass sync'
|
157
|
+
sleep 1 # Allow sync to finish
|
158
|
+
end
|
159
|
+
|
160
|
+
# @note lpass export [--sync=auto|now|no] [--color=auto|never|always]
|
161
|
+
def self.export
|
162
|
+
Utils.cmd 'lpass export'
|
163
|
+
end
|
164
|
+
|
165
|
+
# @note lpass import [CSV_FILENAME]
|
166
|
+
def self.import( csv_filename )
|
167
|
+
Utils.cmd "lpass import '#{escape( csv_filename )}'"
|
168
|
+
end
|
169
|
+
|
170
|
+
# @note lpass share subcommand sharename ...
|
171
|
+
def self.share
|
172
|
+
not_implemented!
|
173
|
+
end
|
174
|
+
|
175
|
+
# @note lpass --version
|
176
|
+
def self.version
|
177
|
+
Utils.cmd 'lpass --version'
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# For methods that are not yet implemented
|
183
|
+
#
|
184
|
+
# @raise Command not implemented
|
185
|
+
def self.not_implemented!
|
186
|
+
raise 'Command not implemented!'
|
187
|
+
end
|
188
|
+
|
189
|
+
# Escapes single or double quotes in strings
|
190
|
+
#
|
191
|
+
# @param string [String] the string to escape
|
192
|
+
# @param double [Boolean] whether to escape double quotes (defaults to single quotes)
|
193
|
+
# @return [String] escaped string
|
194
|
+
def self.escape( string, double: false )
|
195
|
+
if double
|
196
|
+
string.to_s.gsub( '"', '\"' )
|
197
|
+
else # Assume single quotes
|
198
|
+
string.to_s.gsub( "'", "\\\\'" )
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Remove sync files, which can get stuck because of lpass bug
|
203
|
+
def self.remove_sync_files
|
204
|
+
puts 'Removing sync files!'.red if Lastpass.verbose
|
205
|
+
sleep 5 # Hopefully any prior syncs will finish before clearning out stuck ones
|
206
|
+
Utils.cmd "rm -f #{UPLOAD_QUEUE_PATH}/*"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Client
|
3
|
+
|
4
|
+
def initialize( verbose: false )
|
5
|
+
Lastpass.verbose = verbose
|
6
|
+
end
|
7
|
+
|
8
|
+
def login( email:, password: )
|
9
|
+
if logged_in?
|
10
|
+
Cli.sync
|
11
|
+
return true
|
12
|
+
end
|
13
|
+
response = Cli.login( email, password: password )
|
14
|
+
raise "Login failed! #{response}" unless response.include? 'Success'
|
15
|
+
Cli.sync
|
16
|
+
@password = nil # Clear out password
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def logout
|
21
|
+
return true if logged_out?
|
22
|
+
Cli.logout
|
23
|
+
true
|
24
|
+
rescue
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def logged_in?
|
29
|
+
Cli.status.include? 'Logged in'
|
30
|
+
rescue
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def logged_out?
|
35
|
+
!logged_in?
|
36
|
+
end
|
37
|
+
|
38
|
+
def accounts
|
39
|
+
Accounts.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def groups
|
43
|
+
Groups.new
|
44
|
+
end
|
45
|
+
|
46
|
+
# Hide instance variables and values
|
47
|
+
def inspect
|
48
|
+
original_inspect = super
|
49
|
+
original_inspect.split( ' ' ).first << '>'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Collection
|
3
|
+
|
4
|
+
def find( search_text, with_password: false )
|
5
|
+
show( search_text, with_passwords: with_password )&.first
|
6
|
+
end
|
7
|
+
|
8
|
+
def find_all( search_text = '.*', with_passwords: false )
|
9
|
+
show( search_text, with_passwords: with_passwords, regex: true ) || []
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def show( search_text, regex: false, with_passwords: false )
|
15
|
+
begin
|
16
|
+
response = Cli.show( search_text, expand_multi: true, all: true, basic_regexp: regex )
|
17
|
+
rescue => e
|
18
|
+
if e.message.include? 'Could not find specified account'
|
19
|
+
return nil # Just means nothing was found
|
20
|
+
else
|
21
|
+
raise e # Something is wrong, re-raise the exception
|
22
|
+
end
|
23
|
+
end
|
24
|
+
Parser.parse( response, with_passwords: with_passwords )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Group
|
3
|
+
attr_reader :id
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
def initialize( params )
|
7
|
+
params[:name].chomp!( '/' ) if params[:name]&.end_with? '/'
|
8
|
+
params_to_group( params )
|
9
|
+
end
|
10
|
+
|
11
|
+
def update( params )
|
12
|
+
deleted! if @deleted
|
13
|
+
params.delete( :id ) # Prevent overwriting ID
|
14
|
+
params_to_group( params )
|
15
|
+
save
|
16
|
+
end
|
17
|
+
|
18
|
+
def save
|
19
|
+
deleted! if @deleted
|
20
|
+
# If there is an ID, update that entry
|
21
|
+
if @id
|
22
|
+
Cli.edit_group( @id, name: @name )
|
23
|
+
else # If no ID, that means this is a new entry
|
24
|
+
Cli.add_group( @name )
|
25
|
+
set_id_after_save
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete
|
31
|
+
Cli.rm( @id )
|
32
|
+
@deleted = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hash
|
36
|
+
params = {}
|
37
|
+
params[:id] = @id if @id
|
38
|
+
params[:name] = @name if @name
|
39
|
+
params
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method :to_h, :to_hash
|
43
|
+
|
44
|
+
# Hide instance variables and values
|
45
|
+
def inspect
|
46
|
+
original_inspect = super
|
47
|
+
original_inspect.split( ' ' ).first << '>'
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def params_to_group( params )
|
53
|
+
@id = params[:id] if params[:id]
|
54
|
+
@name = params[:name] if params[:name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_id_after_save
|
58
|
+
response = Cli.show( "#{@name}/", id: true )
|
59
|
+
if response.nil? || response == '' || response.include?( 'Multiple matches found' )
|
60
|
+
raise "Unable to fetch ID of newly created group! Name may have not been unique. Response: #{response}"
|
61
|
+
end
|
62
|
+
@id = response
|
63
|
+
end
|
64
|
+
|
65
|
+
def deleted!
|
66
|
+
raise "Group [ID:#{@id}] has been deleted!"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Groups < Collection
|
3
|
+
|
4
|
+
def find( search_text, with_password: false )
|
5
|
+
search_text << '/' unless search_text.end_with? '/'
|
6
|
+
params = super
|
7
|
+
if params.is_a?( Hash ) && params[:name]&.end_with?( '/' )
|
8
|
+
Group.new( params )
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_all( search_text = '.*', with_passwords: false )
|
13
|
+
groups = super.select { |params| params[:name]&.end_with? '/' }
|
14
|
+
groups.map do |params|
|
15
|
+
Group.new( params )
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create( params )
|
20
|
+
params.delete( :id ) # Prevent overwriting ID
|
21
|
+
Group.new( params ).save
|
22
|
+
end
|
23
|
+
|
24
|
+
# Hide instance variables and values
|
25
|
+
def inspect
|
26
|
+
original_inspect = super
|
27
|
+
original_inspect.split( ' ' ).first << '>'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Lastpass
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
def self.parse( raw_string, with_passwords: false )
|
5
|
+
new( raw_string, with_passwords: with_passwords ).parse_all
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize( raw_string, with_passwords: false )
|
9
|
+
@raw_string = raw_string
|
10
|
+
@with_passwords = with_passwords
|
11
|
+
@all = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_all
|
15
|
+
@raw_string.split( "\n" ).each do |line|
|
16
|
+
parse_line( line )
|
17
|
+
end
|
18
|
+
@all.values
|
19
|
+
end
|
20
|
+
|
21
|
+
# Hide instance variables and values
|
22
|
+
def inspect
|
23
|
+
original_inspect = super
|
24
|
+
original_inspect.split( ' ' ).first << '>'
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_line( line )
|
30
|
+
parent_match = line.match( /^(.+)\s\[id:\s(\d+)\]$/ )
|
31
|
+
child_match = line.match( /^((?!\s\[id:\s)[\s\S])*$/ )
|
32
|
+
if parent_match
|
33
|
+
parent_id = parent_match[2]
|
34
|
+
parent_name = parent_match[1]
|
35
|
+
@all[parent_id] = { id: parent_id, name: parent_name }
|
36
|
+
@parent_id = parent_id
|
37
|
+
elsif child_match
|
38
|
+
child_parsed_match = child_match[0].match( /^(\w+):\s(.*)$/ )
|
39
|
+
if child_parsed_match && @parent_id
|
40
|
+
key = child_parsed_match[1]
|
41
|
+
value = child_parsed_match[2]
|
42
|
+
@all[@parent_id] ||= {}
|
43
|
+
return if !@with_passwords && key.downcase.to_sym == :password
|
44
|
+
@all[@parent_id][key.downcase.to_sym] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Lastpass
|
5
|
+
class Utils
|
6
|
+
|
7
|
+
# Run a command
|
8
|
+
def self.cmd( command, output: false )
|
9
|
+
puts "RUN COMMAND: #{command}".green if Lastpass.verbose
|
10
|
+
@stdout = ''
|
11
|
+
Open3::popen3( command ) do |stdin, stdout, stderr, wait_thr|
|
12
|
+
stdout.sync = true
|
13
|
+
while line = stdout.gets
|
14
|
+
puts line if Lastpass.verbose || output
|
15
|
+
@stdout << line
|
16
|
+
end
|
17
|
+
|
18
|
+
exit_status = wait_thr.value
|
19
|
+
unless exit_status.success?
|
20
|
+
puts "COMMAND: #{command}".red if Lastpass.verbose
|
21
|
+
stderr_text = stderr.read
|
22
|
+
puts stderr_text.red if Lastpass.verbose
|
23
|
+
raise StandardError, "Command: '#{command}', Stdout: '#{stdout.read}', Stderr: '#{stderr_text}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
return @stdout
|
28
|
+
rescue Errno::ENOENT => e
|
29
|
+
puts "COMMAND: #{command}".red if Lastpass.verbose
|
30
|
+
puts "#{e}".red if Lastpass.verbose
|
31
|
+
raise StandardError, "Command: '#{command}', Error: '#{e}'"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lastpass-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Terry
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Full access to the LastPass vault to create, read, and update account
|
84
|
+
information and credentials.
|
85
|
+
email:
|
86
|
+
- eterry1388@aol.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
|
+
- CODE_OF_CONDUCT.md
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/console
|
100
|
+
- lastpass-api.gemspec
|
101
|
+
- lib/lastpass-api.rb
|
102
|
+
- lib/lastpass-api/account.rb
|
103
|
+
- lib/lastpass-api/accounts.rb
|
104
|
+
- lib/lastpass-api/cli.rb
|
105
|
+
- lib/lastpass-api/client.rb
|
106
|
+
- lib/lastpass-api/collection.rb
|
107
|
+
- lib/lastpass-api/group.rb
|
108
|
+
- lib/lastpass-api/groups.rb
|
109
|
+
- lib/lastpass-api/parser.rb
|
110
|
+
- lib/lastpass-api/utils.rb
|
111
|
+
- lib/lastpass-api/version.rb
|
112
|
+
homepage: http://www.entretechno.com
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.5.1
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: Read/Write access to the online LastPass vault using LastPass CLI
|
136
|
+
test_files: []
|