diff --git a/.docker-test b/.docker-test deleted file mode 100644 index d36fad8..0000000 --- a/.docker-test +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -ruby_version=${1:-2.4.2} - -if ! rvm use ruby-${ruby_version} &>/dev/null ; then - echo "The ruby version '${ruby_version}' doesn't exist!" - echo "Available versions are:" - rvm list rubies strings | cut -d '-' -f2 - exit 2 -fi - -echo '# ---------------------------------' -echo "# Use ruby version: ${ruby_version}" -echo '# ---------------------------------' - -cp -r /mpw ~/mpw -cd ~/mpw -gem install bundler --no-ri --no-rdoc -bundle install -gem build mpw.gemspec -gem install mpw-$(cat VERSION).gem -cp -a /dev/urandom /dev/random - -rubocop -ruby ./test/init.rb -ruby ./test/test_config.rb -ruby ./test/test_item.rb -ruby ./test/test_mpw.rb -ruby ./test/test_translate.rb -ruby ./test/init.rb -ruby ./test/test_cli.rb -ruby ./test/test_import.rb diff --git a/.gitignore b/.gitignore index afd83c3..b844b14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ Gemfile.lock -*.gem -.yardoc -doc diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 56d1540..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,38 +0,0 @@ - -AllCops: - Exclude: - - db/**/* - - config/**/* - - Vagrantfile - TargetRubyVersion: 2.3 - -Naming/AccessorMethodName: - Enabled: false - -Lint/RescueWithoutErrorClass: - Enabled: false - -Metrics/LineLength: - Max: 120 -Metrics/CyclomaticComplexity: - Enabled: false -Metrics/PerceivedComplexity: - Enabled: false -Metrics/MethodLength: - Enabled: false -Metrics/BlockLength: - Enabled: false -Metrics/ClassLength: - Enabled: false -Metrics/AbcSize: - Enabled: false - -Style/NumericLiteralPrefix: - Enabled: false -Style/FrozenStringLiteralComment: - Enabled: false -Style/CommandLiteral: - Enabled: true - EnforcedStyle: percent_x -Style/Documentation: - Enabled: false diff --git a/.travis.yml b/.travis.yml index 14d696a..141eab1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,13 @@ language: ruby -dist: precise rvm: - - 2.4.2 - - 2.3.5 - - 2.2.8 + - 2.3.1 + - 2.2.5 - 2.1.10 install: - - sudo cp -a /dev/urandom /dev/random - - sudo apt-get purge -y gnupg-agent gnupg2 - bundle install + - gem install 'test-unit' + - echo 9999 > VERSION - gem build mpw.gemspec - - gem install mpw-$(cat VERSION).gem + - gem install mpw-9999.gem script: - - rubocop - - ruby ./test/init.rb - - ruby ./test/test_config.rb - - ruby ./test/test_item.rb - - ruby ./test/test_mpw.rb - - ruby ./test/test_translate.rb - - ruby ./test/init.rb - - ruby ./test/test_cli.rb - - ruby ./test/test_import.rb + - ruby ./test/tests.rb diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..261d083 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,43 @@ += CHANGELOG = +== v4.0.0 (beta) == + +* new interface with a table +* new command line interface +* use text editor for add or update an item +* several bugs fix + +== v3.2.1 == + +* fix bug when add a new item + +== v3.2.0 == + +* add support OTP +* fix bug in synchronize +* improve interface + +== v3.1.0 == + +* add clipboard +* can change gpg version +* minor change in interface +* several bugs fix + +== v3.0.0 == + +* new storage format +* new share system +* remove MPW server + +== v2.0.0 == + +* change format csv to yaml +* easy install with gem +* add sync with ftp and ssh +* many improvement + +== v1.1.0 == + +* Add sync with MPW Server +* Add MPW Server +* Fix minors bugs diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 199b863..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,98 +0,0 @@ -# CHANGELOG -## v4.2.2 (2017-08-15) - - * minor improvements in the interface - -## v4.2.1 (2017-07-30) - - * fix bug in otp generator - -## v4.2.0 (2017-06-06) - - * feat: improve the interface - * feat: add copy url - * feat: add unit tests for cli - * feat: comment the code with yarn syntax - * fix several bugs - * fix translations - -## v4.1.1 (2017-05-03) - - * fix bug in init - -## v4.1.0 (2017-04-22) - - * feat: add options to update or add an item in command line - * feat: print config - * feat: add a specific path for a wallet - * feat: add rubocop to fix syntax - * fix: pinentry mode with gpg >= 2.1 - * remove SSH and FTP synchronization - -## v4.0.0 (2017-03-09) - - * feature: set default wallet - * add option for generate a random password when you update an item - * fix encryption when you share an existing wallet - * several bugs fix - -## v4.0.0-beta1 (2017-02-16) - - * add manage share key with new interface - -## v4.0.0-beta (2016-11-11) - - * new interface with a table - * new command line interface - * use text editor for add or update an item - * fix generate gpg key with RSA - * several bugs fix - * add unit tests - -## v3.2.1 (2016-08-06) - - * fix bug when add a new item - -## v3.2.0 (2016-08-03) - - * add support OTP - * fix bug in synchronize - * improve interface - -## v3.1.0 (2016-07-09) - - * add clipboard - * can change gpg version - * minor change in interface - * several bugs fix - -## v3.0.0 (2016-07-05) - - * new storage format - * new share system - * remove MPW server - -## v2.0.3 (2015-09-27) - - * add no-sync option - -## v2.0.1 (2015-06-23) - - * fix mpw-ssh - -## v2.0.0 (2015-06-22) - - * change format csv to yaml - * easy install with gem - * add sync with ftp and ssh - * many improvement - -## v1.1.0 (2014-01-28) - - * Add sync with MPW Server - * Add MPW Server - * Fix minors bugs - -## v1.0.0 (2014-01-15) - - * first release diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 6654780..0000000 --- a/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM debian:stretch -MAINTAINER Adrien Waksberg "mpw@yae.im" - -RUN apt update -RUN apt dist-upgrade -y - -RUN apt install -y procps gnupg1 curl git -RUN ln -snvf /usr/bin/gpg1 /usr/bin/gpg -RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB -RUN curl -sSL https://get.rvm.io | bash -s stable -RUN echo 'source "/usr/local/rvm/scripts/rvm"' >> /etc/bash.bashrc - -RUN /bin/bash -l -c "rvm install 2.4.2" -RUN /bin/bash -l -c "rvm install 2.3.5" -RUN /bin/bash -l -c "rvm install 2.2.8" -RUN /bin/bash -l -c "rvm install 2.1.10" diff --git a/Gemfile b/Gemfile index a76b2c1..a588d17 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,10 @@ source 'https://rubygems.org' -gem 'clipboard', '~> 1.1', '>= 1.1.1' -gem 'colorize', '~> 0.8', '>= 0.8.1' -gem 'gpgme', '~> 2.0', '>= 2.0.14' -gem 'highline', '~> 1.7', '>= 1.7.8' -gem 'i18n', '~> 0.9', '>= 0.9.1' -gem 'locale', '~> 2.1', '>= 2.1.2' -gem 'rotp', '~> 3.3', '>= 3.3.0' - -group :development do - gem 'rubocop', '0.50.0' - gem 'test-unit' - gem 'yard' -end +gem "i18n", "~> 0.7", ">= 0.7.0" +gem "gpgme", "~> 2.0", ">= 2.0.12" +gem "highline", "~> 1.7", ">= 1.7.8" +gem "locale", "~> 2.1", ">= 2.1.2" +gem "colorize", "~> 0.8", ">= 0.8.1" +gem "net-ssh", "~> 3.2", ">= 3.2.0" +gem "net-sftp", "~> 2.1", ">= 2.1.2" +gem "clipboard", "~> 1.1", ">= 1.1.1" +gem "rotp", "~> 3.1", ">= 3.1.0" diff --git a/LICENSE b/LICENSE index 0c5ea8e..d159169 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,339 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. - 1. Definitions. + Preamble - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. + The precise terms and conditions for copying, distribution and +modification follow. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, - END OF TERMS AND CONDITIONS + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) - APPENDIX: How to apply the Apache License to your work. +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. - Copyright 2017 Adrien Waksberg + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. - 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 + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. - http://www.apache.org/licenses/LICENSE-2.0 + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. - 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. + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md deleted file mode 100644 index be9e7cb..0000000 --- a/README.md +++ /dev/null @@ -1,195 +0,0 @@ -# MPW: Manage your passwords! -[](https://github.com/nishiki/manage-password/releases) -[](https://travis-ci.org/nishiki/manage-password) -[](https://github.com/nishiki/manage-password/blob/master/LICENSE) - -mpw is a little software which stores your passwords in [GnuPG](http://www.gnupg.org/) encrypted files. - -## Features - - * generate random password - * generate OTP code - * copy your login, password or otp in clipboard - * manage many wallets - * share a wallet with others GPG keys - -## Install - -On debian or ubuntu: -``` -apt install ruby ruby-dev xclip -gem install mpw -``` - -## How to use -### First steps - -Initialize your first wallet: -``` -mpw config --init user@host.com -``` - -Add your first item: -``` -mpw add --host assurance.com --port 443 --user user_2132 --protocol https --random -mpw add --host fric.com --user 230403 --otp-code 23434113 --protocol https --comment 'I love my bank' --random -``` - -And list your items: -``` -mpw list -``` -or search an item with -``` -mpw list --pattern love -mpw list --group bank -``` - -Output: -``` -Assurance - ========================================================================== - ID | Host | User | OTP | Comment - ========================================================================== - 1 | https://assurance.com:443 | user_2132 | | - -Bank - ========================================================================== - ID | Host | User | OTP | Comment - ========================================================================== - 3 | https://fric.com | 230403 | X | I love my bank -``` - -Copy a password, login or OTP code: -``` -mpw copy -p assurance.com -``` - -Update an item: -``` -mpw update -p assurance.com -``` - -Delete an item: -``` -mpw delete -p assurance.com -``` - -### Manage wallets - -List all available wallets: -``` -mpw wallet -``` - -List all GPG keys in wallet: -``` -mpw wallet --list-keys [--wallet NAME] -``` - -Share with an other GPG key: -``` -mpw wallet --add-gpg-key test42@localhost.com - or -mpw wallet --add-gpg-key /path/to/file -``` - -Remove a GPG key: -``` -mpw wallet --delete-gpg-key test42@localhost.com -``` - -### Export and import data - -You can export your data in yaml file with your passwords in clear text: -``` -mpw export --file export.yml -``` - -Import data from an yaml file: -``` -mpw import --file import.yml -``` - -Example yaml file for mpw: - -``` ---- -1: - host: fric.com - user: 230403 - group: Bank - password: 5XdiTQOubRDw9B0aJoMlcEyL - protocol: https - port: - otp_key: 330223432 - comment: I love my bank -2: - host: assurance.com - user: user_2132 - group: Assurance - password: DMyK6B3v4bWO52VzU7aTHIem - protocol: https - port: 443 - otp_key: - comment: -``` - -### Config - -Print the current config -``` -mpw config -``` - -Output: - -``` -Configuration - ============================================== - lang | fr - gpg_key | mpw@yae.im - default_wallet | - config_dir | /home/mpw/.config/mpw - pinmode | true - gpg_exe | - path_wallet_test | /tmp/test.mpw - password_numeric | true - password_alpha | true - password_special | false - password_length | 16 - -``` - -## Development - -Don't run the tests on your local machine, you risk to lost your datas. - -### Test on local machine with docker - - * install [docker](https://docs.docker.com/engine/installation/) - * run the tests - -``` -docker run -v $(pwd):/mpw:ro -it nishiki/ruby:stretch /bin/bash -l /mpw/.docker-test -``` - -## License - -``` -* Author:: Adrien Waksberg <mpw@yae.im> - -Copyright (c) 2013-2017 Adrien Waksberg - -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. -``` diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..17725d2 --- /dev/null +++ b/README.rst @@ -0,0 +1,54 @@ +MPW: Manage your passwords! +******************************************************* +|Version| |Build Status| |License| + +mpw is a little software which stores your passwords in `GnuPG <http://www.gnupg.org/>`_ encrypted files. + +Features +======== + +* generate OTP code +* synchronize your passwords with SSH or FTP. +* copy your login, password or otp in clipboard + +Install +======= + +On debian or ubuntu:: + + apt install ruby ruby-dev xclip + gem install mpw + + +How to use +========== + +A simple mpw usage:: + + mpw config --init user@host.com + mpw add + mpw copy + mpw add + mpw list + +Output:: + + Bank + ============================================================================== + ID | Host | User | Protocol | Port | OTP | Comment + ============================================================================== + 1 | bank.com | 1234456 | https | | X | + + Linux + ============================================================================== + ID | Host | User | Protocol | Port | OTP | Comment + ============================================================================== + 2 | linuxfr.org | example | https | | | Da Linux French Site + + +.. |Version| image:: https://img.shields.io/badge/latest_version-4.0.0--beta-yellow.svg + :target: https://github.com/nishiki/manage-password/releases +.. |License| image:: https://img.shields.io/badge/license-GPL--2.0-blue.svg + :target: https://github.com/nishiki/manage-password/blob/master/LICENSE +.. |Build Status| image:: https://travis-ci.org/nishiki/manage-password.svg?branch=master + :target: https://travis-ci.org/nishiki/manage-password diff --git a/VERSION b/VERSION index af8c8ec..c0de572 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.2.2 +4.0.0-beta diff --git a/bin/mpw b/bin/mpw index 11bc726..8e578f8 100755 --- a/bin/mpw +++ b/bin/mpw @@ -1,30 +1,26 @@ -#!/usr/bin/env ruby -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -$LOAD_PATH << File.expand_path('../../lib', __FILE__) +$: << File.expand_path('../../lib', __FILE__) require 'locale' require 'set' require 'i18n' -require 'colorize' # --------------------------------------------------------- # # Set local @@ -33,7 +29,7 @@ require 'colorize' lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] if defined?(I18n.enforce_available_locales) - I18n.enforce_available_locales = true + I18n.enforce_available_locales = true end I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) @@ -48,25 +44,21 @@ I18n.locale = lang.to_sym bin_dir = File.dirname(__FILE__) command = "#{bin_dir}/mpw-#{ARGV[0]}" -if Dir.glob("#{bin_dir}/mpw-*").include?(command.to_s) - begin - Kernel.load(command) - rescue OptionParser::ParseError => e - puts "#{I18n.t('display.error')}: #{e}".red - end +if Dir.glob("#{bin_dir}/mpw-*").include?("#{command}") + Kernel.load(command) else - puts "#{I18n.t('option.usage')}: mpw COMMAND [options]\n\n" - puts 'Commands:' - puts " add #{I18n.t('command.add')}" - puts " config #{I18n.t('command.config')}" - puts " copy #{I18n.t('command.copy')}" - puts " delete #{I18n.t('command.delete')}" - puts " export #{I18n.t('command.export')}" - puts " genpwd #{I18n.t('command.genpwd')}" - puts " import #{I18n.t('command.import')}" - puts " list #{I18n.t('command.list')}" - puts " update #{I18n.t('command.update')}" - puts " wallet #{I18n.t('command.wallet')}" + puts "#{I18n.t('option.usage')}: mpw COMMAND [options]\n\n" + puts 'Commands:' + puts " add #{I18n.t('command.add')}" + puts " config #{I18n.t('command.config')}" + puts " copy #{I18n.t('command.copy')}" + puts " delete #{I18n.t('command.delete')}" + puts " export #{I18n.t('command.export')}" + puts " genpwd #{I18n.t('command.genpwd')}" + puts " import #{I18n.t('command.import')}" + puts " list #{I18n.t('command.list')}" + puts " update #{I18n.t('command.update')}" + puts " wallet #{I18n.t('command.wallet')}" - exit 3 + exit 3 end diff --git a/bin/mpw-add b/bin/mpw-add index e08caae..5322cc1 100644 --- a/bin/mpw-add +++ b/bin/mpw-add @@ -1,22 +1,20 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -26,59 +24,38 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -values = {} -options = {} -options[:text_editor] = true +options = {} +options[:sync] = true OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw add [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw add [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-C', '--comment COMMENT', I18n.t('option.comment')) do |comment| - values[:comment] = comment - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-G', '--group NAME', I18n.t('option.new_group')) do |group| - values[:group] = group - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-r', '--random', I18n.t('option.random_password')) do + options[:password] = true + end - opts.on('-o', '--otp-code CODE', I18n.t('option.otp_code')) do |otp| - values[:otp_key] = otp - end - - opts.on('-r', '--random', I18n.t('option.random_password')) do - options[:password] = true - end - - opts.on('-t', '--text-editor', I18n.t('option.text_editor')) do - options[:text_editor] = true - end - - opts.on('-u', '--url URL', I18n.t('option.url')) do |url| - values[:url] = url - end - - opts.on('-U', '--user USER', I18n.t('option.user')) do |user| - values[:user] = user - end - - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) cli.decrypt -cli.add(options[:password], options[:text_editor], values) +cli.add(options[:password]) diff --git a/bin/mpw-config b/bin/mpw-config index 2ac0f77..120da5b 100644 --- a/bin/mpw-config +++ b/bin/mpw-config @@ -1,22 +1,20 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -30,92 +28,48 @@ options = {} values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw config [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw config [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-d', '--default-wallet NAME', I18n.t('option.default_wallet')) do |default_wallet| - values[:default_wallet] = default_wallet - end + opts.on('-g', '--gpg-exe PATH', I18n.t('option.gpg_exe')) do |gpg_exe| + values[:gpg_exe] = gpg_exe + end - opts.on('-g', '--gpg-exe PATH', I18n.t('option.gpg_exe')) do |gpg_exe| - values[:gpg_exe] = gpg_exe - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-i', '--init GPG_KEY', I18n.t('option.init')) do |gpg_key| + options[:init] = true + values[:gpg_key] = gpg_key + end - opts.on('-i', '--init GPG_KEY', I18n.t('option.init')) do |gpg_key| - options[:init] = true - values[:gpg_key] = gpg_key - end + opts.on('-k', '--key GPG_KEY', I18n.t('option.gpg_key')) do |gpg_key| + values[:gpg_key] = gpg_key + end - opts.on('-k', '--key GPG_KEY', I18n.t('option.gpg_key')) do |gpg_key| - values[:gpg_key] = gpg_key - end + opts.on('-l', '--lang LANG', I18n.t('option.lang')) do |lang| + values[:lang] = lang + end - opts.on('-L', '--lang LANG', I18n.t('option.lang')) do |lang| - values[:lang] = lang - end - - opts.on('-P', '--enable-pinmode', I18n.t('option.pinmode')) do - values[:pinmode] = true - end - - opts.on('-p', '--disable-pinmode', I18n.t('option.disable_pinmode')) do - values[:pinmode] = false - end - - opts.on('-w', '--wallet-dir PATH', I18n.t('option.wallet_dir')) do |wallet_dir| - values[:wallet_dir] = wallet_dir - end - - opts.on('-l', '--length NUMBER', I18n.t('option.length')) do |length| - values[:pwd_length] = length.to_i - end - - opts.on('-n', '--numeric', I18n.t('option.numeric')) do - values[:pwd_numeric] = true - end - - opts.on('-N', '--disable-numeric', I18n.t('option.disable_numeric')) do - values[:pwd_numeric] = false - end - - opts.on('-s', '--special-chars', I18n.t('option.special_chars')) do - values[:pwd_special] = true - end - - opts.on('-S', '--disable-special-chars', I18n.t('option.special_chars')) do - values[:pwd_special] = false - end - - opts.on('-a', '--alpha', I18n.t('option.alpha')) do - values[:pwd_alpha] = true - end - - opts.on('-A', '--disable-alpha', I18n.t('option.disable_alpha')) do - values[:pwd_alpha] = false - end + opts.on('-w', '--wallet-dir PATH', I18n.t('option.wallet_dir')) do |wallet_dir| + values[:wallet_dir] = wallet_dir + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, nil) -if options.key?(:init) - cli.setup(values) - cli.load_config - cli.get_wallet - cli.setup_gpg_key(values[:gpg_key]) +if not options[:init].nil? + cli.setup(values) + cli.load_config + cli.get_wallet + cli.setup_gpg_key(options[:init]) + cli.setup_wallet_config else - cli.load_config - if values.empty? - cli.list_config - else - cli.set_config(values) - end + cli.set_config(values) end diff --git a/bin/mpw-copy b/bin/mpw-copy index fc3b6e0..0a14ed7 100644 --- a/bin/mpw-copy +++ b/bin/mpw-copy @@ -1,22 +1,20 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -27,40 +25,45 @@ require 'mpw/cli' # --------------------------------------------------------- # options = {} +options[:sync] = true options[:clipboard] = true values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw copy [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw copy [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-d', '--disable-clipboard', I18n.t('option.clipboard')) do - options[:clipboard] = false - end + opts.on('-d', '--disable-clipboard', I18n.t('option.clipboard')) do + options[:clipboard] = false + end - opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| - values[:group] = group - end + opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| + values[:group] = group + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| - values[:pattern] = pattern - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| + values[:pattern] = pattern + end + + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) diff --git a/bin/mpw-delete b/bin/mpw-delete index 48eb792..2d79e6a 100644 --- a/bin/mpw-delete +++ b/bin/mpw-delete @@ -1,22 +1,20 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -26,36 +24,41 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -options = {} -values = {} +options = {} +options[:sync] = true +values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw delete [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw delete [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| - values[:group] = group - end + opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| + values[:group] = group + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| - values[:pattern] = pattern - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| + values[:pattern] = pattern + end + + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) diff --git a/bin/mpw-export b/bin/mpw-export index 92eb7bd..f891873 100644 --- a/bin/mpw-export +++ b/bin/mpw-export @@ -1,19 +1,20 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -23,40 +24,45 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -options = {} -values = {} +options = {} +options[:sync] = true +values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-f', '--file PATH', I18n.t('option.file_export')) do |file| - options[:file] = file - end + opts.on('-f', '--file PATH', I18n.t('option.file_export')) do |file| + options[:file] = file + end - opts.on('-g', '--group GROUP', I18n.t('option.group')) do |group| - values[:group] = group - end + opts.on('-g', '--group GROUP', I18n.t('option.group')) do |group| + values[:group] = group + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| - values[:pattern] = pattern - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| + values[:pattern] = pattern + end + + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) diff --git a/bin/mpw-genpwd b/bin/mpw-genpwd index f6ca795..8af4290 100644 --- a/bin/mpw-genpwd +++ b/bin/mpw-genpwd @@ -1,19 +1,20 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/mpw' @@ -21,29 +22,29 @@ require 'mpw/mpw' options = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw passwd [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw passwd [options]" - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-l', '--length NUMBER', I18n.t('option.length')) do |length| - options[:length] = length.to_i - end + opts.on('-l', '--length NUMBER', I18n.t('option.length')) do |length| + options[:length] = length.to_i + end - opts.on('-n', '--numeric', I18n.t('option.numeric')) do - options[:numeric] = true - end + opts.on('-n', '--numeric', I18n.t('option.numeric')) do + options[:numeric] = true + end - opts.on('-s', '--special-chars', I18n.t('option.special_chars')) do - options[:special] = true - end + opts.on('-s', '--special-chars', I18n.t('option.special_chars')) do + options[:special] = true + end - opts.on('-a', '--alpha', I18n.t('option.alpha')) do - options[:alpha] = true - end + opts.on('-a', '--alpha', I18n.t('option.alpha')) do + options[:alpha] = true + end end.parse! -puts MPW::MPW.password(options) +puts MPW::MPW::password(options) exit 0 diff --git a/bin/mpw-import b/bin/mpw-import index d0deae9..c740252 100644 --- a/bin/mpw-import +++ b/bin/mpw-import @@ -1,19 +1,20 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -23,43 +24,38 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -formats = - Dir["#{File.expand_path('../../lib/mpw/import', __FILE__)}/*.rb"] - .map { |v| File.basename(v, '.rb') } - .join(', ') -options = { - format: 'mpw' -} +options = {} +options[:sync] = true OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw import [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw import [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-f', '--file PATH', I18n.t('option.file_import')) do |file| - options[:file] = file - end + opts.on('-f', '--file PATH', I18n.t('option.file_import')) do |file| + options[:file] = file + end - opts.on('-F', '--format STRING', I18n.t('option.file_format', formats: formats)) do |format| - options[:format] = format - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) cli.decrypt -cli.import(options[:file], options[:format]) +cli.import(options[:file]) diff --git a/bin/mpw-list b/bin/mpw-list index fb7899c..9d18b0d 100644 --- a/bin/mpw-list +++ b/bin/mpw-list @@ -1,19 +1,20 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -23,36 +24,41 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -options = {} -values = {} +options = {} +options[:sync] = true +values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw list [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw list [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| - values[:group] = group - end + opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| + values[:group] = group + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| - values[:pattern] = pattern - end + opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| + values[:pattern] = pattern + end - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end + + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) diff --git a/bin/mpw-update b/bin/mpw-update index 26a55c9..301b876 100644 --- a/bin/mpw-update +++ b/bin/mpw-update @@ -1,19 +1,20 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -23,70 +24,43 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -values = {} -search = {} -options = {} -options[:text_editor] = false +options = {} +options[:sync] = true +values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw update [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw update [options]" - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-C', '--comment COMMENT', I18n.t('option.comment')) do |comment| - values[:comment] = comment - end + opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| + values[:group] = group + end - opts.on('-g', '--group NAME', I18n.t('option.group')) do |group| - search[:group] = group - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-G', '--new-group NAME', I18n.t('option.new_group')) do |group| - values[:group] = group - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| + values[:pattern] = pattern + end - opts.on('-o', '--otp-code CODE', I18n.t('option.otp_code')) do |otp| - values[:otp_key] = otp - end - - opts.on('-p', '--pattern PATTERN', I18n.t('option.pattern')) do |pattern| - search[:pattern] = pattern - end - - opts.on('-r', '--random', I18n.t('option.random_password')) do - options[:password] = true - end - - opts.on('-t', '--text-editor', I18n.t('option.text_editor')) do - options[:text_editor] = true - end - - opts.on('-u', '--url URL', I18n.t('option.url')) do |url| - values[:url] = url - end - - opts.on('-U', '--user USER', I18n.t('option.user')) do |user| - values[:user] = user - end - - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) - -options[:text_editor] = true if values.empty? +cli = MPW::Cli.new(config, options[:sync]) cli.load_config cli.get_wallet(options[:wallet]) cli.decrypt -cli.update(options[:password], options[:text_editor], search, values) +cli.update(values) diff --git a/bin/mpw-wallet b/bin/mpw-wallet index 6518283..b81e6b2 100644 --- a/bin/mpw-wallet +++ b/bin/mpw-wallet @@ -1,19 +1,20 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'optparse' require 'mpw/config' @@ -23,68 +24,68 @@ require 'mpw/cli' # Options # --------------------------------------------------------- # -options = {} -options[:delete] = false +options = {} +options[:sync] = {} +values = {} OptionParser.new do |opts| - opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]" + opts.banner = "#{I18n.t('option.usage')}: mpw wallet [options]" - opts.on('-a', '--add-gpg-key NAME', I18n.t('option.add_gpg_key')) do |gpg_key| - options[:gpg_key] = gpg_key - end + opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| + options[:config] = config + end - opts.on('-c', '--config PATH', I18n.t('option.config')) do |config| - options[:config] = config - end + opts.on('-h', '--help', I18n.t('option.help')) do + puts opts + exit 0 + end - opts.on('-d', '--delete-gpg-key NAME', I18n.t('option.delete_gpg_key')) do |gpg_key| - options[:gpg_key] = gpg_key - options[:delete] = true - end + opts.on('--host NAME', I18n.t('option.host')) do |host| + values[:host] = host + end - opts.on('-h', '--help', I18n.t('option.help')) do - puts opts - exit 0 - end + opts.on('-l', '--list', I18n.t('option.list')) do |list| + options[:list] = true + end - opts.on('-l', '--list', I18n.t('option.list')) do - options[:list] = true - end + opts.on('-n', '--no-sync', I18n.t('option.no_sync')) do + options[:sync] = false + end - opts.on('-L', '--list-keys', I18n.t('option.list_keys')) do - options[:list_keys] = true - end + opts.on('--password', I18n.t('option.password')) do + values[:password] = true + end - opts.on('-p', '--path PATH', I18n.t('option.path')) do |path| - options[:path] = path - end + opts.on('--path PATH', I18n.t('option.path')) do |path| + values[:path] = path + end - opts.on('-P', '--default-path', I18n.t('option.default_path')) do - options[:path] = 'default' - end + opts.on('--port NUMBER', I18n.t('option.port')) do |port| + values[:port] = port + end - opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| - options[:wallet] = wallet - end + opts.on('--protocol NAME', I18n.t('option.protocol')) do |protocol| + values[:protocol] = protocol + end + + opts.on('--user NAME', I18n.t('option.user')) do |user| + values[:user] = user + end + + opts.on('-w', '--wallet NAME', I18n.t('option.wallet')) do |wallet| + options[:wallet] = wallet + end end.parse! config = MPW::Config.new(options[:config]) -cli = MPW::Cli.new(config) +cli = MPW::Cli.new(config, options[:sync]) cli.load_config -if options.key?(:path) - cli.get_wallet(options[:wallet]) - cli.set_wallet_path(options[:path]) -elsif options.key?(:list_keys) || options.key?(:gpg_key) - cli.get_wallet(options[:wallet]) - cli.decrypt - - if options.key?(:list_keys) - cli.list_keys - elsif options.key?(:gpg_key) - options[:delete] ? cli.delete_key(options[:gpg_key]) : cli.add_key(options[:gpg_key]) - end +if not options[:list].nil? + cli.list_wallet else - cli.list_wallet + cli.get_wallet(options[:wallet]) + cli.decrypt + cli.setup_wallet_config(values) end diff --git a/i18n/en.yml b/i18n/en.yml index 9df7bda..93955d5 100644 --- a/i18n/en.yml +++ b/i18n/en.yml @@ -1,31 +1,37 @@ --- en: error: - bad_class: "The object class isn't valid!" config: write: "Can't write the config file!" load: "Checkconfig failed!" - key_bad_format: "The key string isn't in the right format!" - no_key_public: "You haven't entered the public key of %{key}!" + key_bad_format: "The key string isn't in good format!" + no_key_public: "You haven't the public key of %{key}!" genkey_gpg: exception: "Can't create the GPG key!" name: "You must define a name for your GPG key!" password: "You must define a password for your GPG key!" - empty: "The class is empty" export: "Can't export, unable to write in %{file}!" - export_key: "Can't export the GPG key" gpg_file: decrypt: "Can't decrypt file!" encrypt: "Can't encrypt the GPG file!" mpw_file: - read_data: "Can't read the MPW file!" - write_data: "Can't write the MPW file!" + read_data: "Can't to read the MPW file!" + write_data: "Can't to write the MPW file!" import: "Can't import, unable to read %{file}!" update: - host_and_comment_empty: "You must define a host or a comment!" + name_empty: "You must define a name!" + sync: + general: "An error has appeared during the sync" + connection: "Connection fail!" + communication: "A communication problem with the server is appeared!" + download: "Can't download the file!" + not_authorized: "You haven't the access to remote file!" + upload: "Can't upload the file on the server!" + unknown: "An unknown error is occured!" + unknown_type: "The sync type is unknown" warning: - select: 'Your choice is not a valid item!' + select: 'Your choice is not a valid element!' command: add: "Add a new item" @@ -41,68 +47,53 @@ en: option: add: "Add an item or key" - add_gpg_key: "Share the wallet with another GPG key" - alpha: "Use letter to create a password" - comment: "Specify a comment" + alpha: "Use letter to generate a password" config: "Specify the configuration file to use" clipboard: "Disable the clipboard feature" - default_path: "Move the wallet to the default directory" - default_wallet: "Specify the default wallet to use" - delete_gpg_key: "Delete wallet sharing with an other GPG key" - disable_alpha: "Don't use letters to create a password" - disable_numeric: "Don't use numbers to generate a password" - disable_pinmode: "Disable the pinentry mode" - disable_special_chars: "Don't use special char to create a password" export: "Export a wallet in an yaml file" - file_export: "Specify the file to export data" - file_format: "Format of import file (default: mpw; available: %{formats})" + file_export: "Specify the file where export data" file_import: "Specify the file to import" - force: "Do not ask confirmation when deleting an item" - generate_password: "Create a random password (default 8 characters)" + force: "No ask to confirm when you delete an item" + generate_password: "Generate a random password (default 8 characters)" gpg_exe: "Set the gpg binary path to use" gpg_key: "Specify a GPG key (ex: user@example.com)" group: "Search the items with specified group" help: "Show this help message" + host: "Specify the server for the synchronization" init: "Initialize mpw" - import: "Import item from an yaml file" - key: "Define the key name" + import: "Import item since a yaml file" + key: "Specify the key name" lang: "Set the software language" - length: "Size of the password" list: "List the wallets" - list_keys: "List the GPG keys in wallet" - new_group: "Define a group for the item" - numeric: "Use number to create a password" - otp_code: "Set an otp key" - path: "Move the wallet in new specify directory" + no_sync: "Disable synchronization with the server" + numeric: "Use number to generate a password" + password: "Change the password for the synchronization" + path: "Specify the remote path" pattern: "Given search pattern" - pinmode: "Enable pinentry mode (available with gpg >= 2.1)" - random_password: "Generate a random password" + port: "Specify the connection port" + protocol: "Specify the protocol for the connection" setup: "Create a new configuration file" setup_wallet: "Create a new configuration file for a wallet" - special_chars: "Use special char to create a password" - show: "Search and display the items" - show_all: "Listing all items" - text_editor: "Use text editor to edit the item" - usage: "Use" - url: "Set an url (ex: https://example.com/path)" - user: "Set an user" + special_chars: "Use special char to generate a password" + show: "Search and show the items" + show_all: "List all items" + usage: "Usage" + user: "Specify the user for the connection" wallet: "Specify a wallet to use" wallet_dir: "Set the wallets folder" form: - select: - choice: "Select the item: " - error: "No item selected" + select: "Select the item: " add_key: valid: "Key has been added!" add_item: - name: "Item name (mandatory)" - group: "Group name" - host: "Hostname or ip" - protocol: "Connection protocol (ssh, http, ...)" - login: "Connection ID" - password: "Password" - port: "Connection port" + name: "The item's name (mandatory" + group: "The group's name" + host: "The hostname or ip" + protocol: "The protocol of the connection (ssh, http, ...)" + login: "The login of connection" + password: "The password" + port: "The connection port" comment: "A comment" otp_key: "The OTP secret" valid: "Item has been added!" @@ -111,11 +102,9 @@ en: clean: "The clipboard has been cleaned." login: "The login has been copied in clipboard." password: "The password has been copied in clipboard for 30s!" - otp: "The OTP code has been copied %{time}s!" - url: "The URL has been copied in clipboard." + otp: "The OTP code has been copied for %{time}s!" help: name: "Help" - url: "Press <u> to copy URL" login: "Press <l> to copy the login" password: "Press <p> to copy the password" otp_code: "Press <o> to copy the otp code" @@ -123,19 +112,12 @@ en: delete_key: valid: "Key has been deleted!" delete_item: - ask: "Are you sure you want to remove this item ?" + ask: "Are you sure you want to remove the item ?" valid: "The item has been removed!" import: ask: "Are you sure you want to import this file %{file} ?" - file_empty: "The import file is empty!" - file_not_exist: "The import file doesn't exist!" - format_unknown: "The import format '%{file_format} is unknown!" - valid: "The import is successful!" + valid: "The import is succesfull!" not_valid: "No data to import!" - set_config: - valid: "The config file has been edited!" - set_wallet_path: - valid: "The wallet has been moved!" setup_config: title: "Setup a new config file" lang: "Choose your language (en, fr, ...) [default=%{lang}]: " @@ -143,38 +125,45 @@ en: gpg_exe: "Enter the executable GPG path (optional): " wallet_dir: "Enter the wallets's folder path [default=%{home}/wallets]: " valid: "The config file has been created!" + setup_wallet: + title: "Wallet setup" + sync_type: "Synchronization type (ssh, ftp): " + sync_host: "Synchronization server: " + sync_port: "Port of the synchronization server: " + sync_user: "Username for the synchronization: " + sync_pwd: "Password for the synchronization: " + sync_path: "File path for the synchronization : " + valid: "The wallet config file has been created!" setup_gpg_key: title: "Setup a GPG key" - ask: "Do you want to create your GPG key ? (Y/n)" - no_create: "You must to create manually your GPG key or relaunch the software." + ask: "Do you want create your GPG key ? (Y/n)" + no_create: "You must create manually your GPG key or relaunch the software." name: "Your name and lastname: " password: "A password for the GPG key: " confirm_password: "Confirm your password: " error_password: "Your passwords aren't identical!" length: "Size of the GPG key [default=2048]: " expire: "Expire time of the GPG key [default=0 (unlimited)]: " - wait: "Please wait until GPG key is created, this process can take a few minutes." + wait: "Please waiting during the GPG key generate, this process can take few minutes." valid: "Your GPG key has been created ;-)" update_item: - name: "Item name (mandatory)" - group: "Group name" - host: "Hostname or ip" - protocol: "Connection protocol (ssh, http, ...)" - login: "Login id" - password: "Password (leave empty if you don't want to update it)" - port: "Connection port" + name: "The item's name (mandatory" + group: "The group's name" + host: "The hostname or ip" + protocol: "The protocol of the connection (ssh, http, ...)" + login: "The login of connection" + password: "The password (leave empty if you don't want change)" + port: "The connection port" comment: "A comment" - otp_key: "Secret OTP (leave empty if you don't want to update it" + otp_key: "The OTP secret (leave empty if you don't want change" valid: "Item has been updated!" export: - valid: "The export in %{file} is successful!" + valid: "The export in %{file} is succesfull!" display: comment: "Comment" - config: "Configuration" error: "ERROR" - keys: "GPG keys" - gpg_password: "GPG password: " + gpg_password: "GPG passphrase: " group: "Group" login: "Login" name: "Name" @@ -185,7 +174,6 @@ en: port: "Port" protocol: "Protocol" server: "Server" - wallets: "Wallets" warning: "Warning" formats: diff --git a/i18n/fr.yml b/i18n/fr.yml index 04583ad..4f8b7ac 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -1,7 +1,6 @@ --- fr: error: - bad_class: "La classe de l'objet n'est pas celle attendue!" config: write: "Impossible d'écrire le fichier de configuration!" load: "Le fichier de configuration est invalide!" @@ -11,9 +10,7 @@ fr: exception: "La création de la clé GPG n'a pas pu aboutir!" name: "Vous devez définir un nom pour votre clé GPG!" password: "Vous devez définir un mot de passe pour votre clé GPG!" - empty: "La classe est vide" export: "Impossible d'exporter les données dans le fichier %{file}!" - export_key: "Impossible d'exporter la clé GPG" gpg_file: decrypt: "Impossible de déchiffrer le fichier GPG!" encrypt: "Impossible de chiffrer le fichier GPG!" @@ -22,7 +19,16 @@ fr: write_data: "Impossible d'écrire le fichier MPW!" import: "Impossible d'importer le fichier %{file}, car il n'est pas lisible!" update: - host_and_comment_empty: "Vous devez définir un host ou un commentaire!" + name_empty: "Vous devez définir un nom!" + sync: + general: "Une erreur est survenue durant la synchronisation" + connection: "La connexion n'a pu être établie!" + communication: "Un problème de communication avec le serveur est apparu!" + download: "Impossible de télécharger le fichier!" + not_authorized: "Vous n'avez pas les autorisations d'accès au fichier distant!" + upload: "Impossible d'envoyer le fichier sur le serveur!" + unknown: "Une erreur inconnue est survenue!" + unknown_type: "Le type de synchronisation est inconnu" warning: select: "Votre choix n'est pas un élément valide!" @@ -41,21 +47,11 @@ fr: option: add: "Ajoute un élément ou une clé" - add_gpg_key: "Partage le portefeuille avec une autre clé GPG" alpha: "Utilise des lettres dans la génération d'un mot de passe" config: "Spécifie le fichier de configuration à utiliser" - comment: "Spécifie un commentaire" clipboard: "Désactive la fonction presse papier" - default_path: "Déplace le portefeuille dans le dossier par défaut" - default_wallet: "Spécifie le porte-feuille à utiliser par défaut" - delete_gpg_key: "Supprime le partage le portefeuille avec une autre clé GPG" - disable_alpha: "Désactive l'utilisation des lettres dans la génération d'un mot de passe" - disable_numeric: "Désactive l'utilisation des chiffre dans la génération d'un mot de passe" - disable_pinmode: "Désactive le mode pinentry" - disable_special_chars: "Désactive l'utilisation des charactères speciaux dans la génération d'un mot de passe" export: "Exporte un portefeuille dans un fichier yaml" file_export: "Spécifie le fichier où exporter les données" - file_format: "Format du fichier d'import (défault: mpw; disponible: %{formats})" file_import: "Spécifie le fichier à importer" force: "Ne demande pas de confirmation pour la suppression d'un élément" generate_password: "Génére un mot de passe aléatoire (défaut 8 caractères)" @@ -63,36 +59,31 @@ fr: gpg_key: "Spécifie une clé GPG (ex: user@example.com)" group: "Recherche les éléments appartenant au groupe spécifié" help: "Affiche ce message d'aide" + host: "Spécifie le serveur pour la synchronisation" import: "Importe des éléments depuis un fichier yaml" init: "Initialise mpw" key: "Spécifie le nom d'une clé" lang: "Spécifie la langue du logiciel (ex: fr)" - length: "Taille du mot de passe" list: "Liste les portefeuilles" - list_keys: "Liste les clés GPG dans le portefeuille" - new_group: "Spécifie le groupe de l'item" + no_sync: "Désactive la synchronisation avec le serveur" numeric: "Utilise des chiffre dans la génération d'un mot de passe" - otp_code: "Spécifie un code OTP" - path: "Déplace le portefeuille dans un nouveau dossier" + password: "Changer le mot de passe de connexion" + path: "Spécifie le chemin distant" pattern: "Motif de donnée à chercher" - pinmode: "Active le mode pinentry (valable avec gpg >= 2.1)" - random_password: "Génére un mot de passe aléatoire" + port: "Spécifie le port de connexion" + protocol: "Spécifie le protocol utilisé pour la connexion" setup: "Création d'un nouveau fichier de configuration" setup_wallet: "Création d'un nouveau fichier de configuration pour un portefeuille" special_chars: "Utilise des charactères speciaux dans la génération d'un mot de passe" show: "Recherche et affiche les éléments" show_all: "Liste tous les éléments" - text_editor: "Active l'édition avec un éditeur de texte" usage: "Utilisation" - url: "Spécifie l'url (ex: http://example.com/path)" - user: "Spécifie un utilisateur" + user: "Spécifie l'identifiant de connection" wallet: "Spécifie le portefeuille à utiliser" wallet_dir: "Spécifie le répertoire des portefeuilles" form: - select: - choice: "Sélectionner l'élément: " - error: "Aucun élément sélectionné" + select: "Sélectionner l'élément: " add_key: valid: "La clé a bien été ajoutée!" add_item: @@ -112,10 +103,8 @@ fr: login: "L'identifiant a été copié dans le presse papier" password: "Le mot de passe a été copié dans le presse papier pour 30s!" otp: "Le code OTP a été copié dans le presse papier il est valable %{time}s!" - url: "L'URL a été copié dans le presse papier" help: name: "Aide" - url: "Pressez <u> pour copier l'URL" login: "Pressez <l> pour copier l'identifiant" password: "Pressez <p> pour copier le mot de passe" otp_code: "Pressez <o> pour copier le code OTP" @@ -127,15 +116,8 @@ fr: valid: "L'élément a bien été supprimé!" import: ask: "Êtes vous sûre de vouloir importer le fichier %{file} ?" - file_empty: "Le fichier d'import est vide!" - file_not_exist: "Le fichier d'import n'existe pas" - format_unknown: "Le format d'import '%{file_format}' est inconnu!" valid: "L'import est un succès!" not_valid: "Aucune donnée à importer!" - set_config: - valid: "Le fichier de configuration a bien été modifié!" - set_wallet_path: - valid: "Le portefeuille a bien été déplacé!" setup_config: title: "Création d'un nouveau fichier de configuration" lang: "Choisissez votre langue (en, fr, ...) [défaut=%{lang}]: " @@ -143,6 +125,15 @@ fr: gpg_exe: "Entrez le chemin de l'exécutable GPG (optionnel): " wallet_dir: "Entrez le chemin du répertoire qui contiendra les porte-feuilles de mot de passe [défaut=%{home}/wallets]: " valid: "Le fichier de configuration a bien été créé!" + setup_wallet: + title: "Configuration du porte-feuille" + sync_type: "Type de synchronisation (ssh, ftp): " + sync_host: "Serveur: " + sync_port: "Port: " + sync_user: "Utilisateur: " + sync_pwd: "Mot de passe: " + sync_path: "Chemin du fichier: " + valid: "Le fichier de configuration du porte-feuille a bien été créé!" setup_gpg_key: title: "Configuration d'une nouvelle clé GPG" ask: "Voulez vous créer votre clé GPG ? (O/n)" @@ -171,9 +162,7 @@ fr: display: comment: "Commentaire" - config: "Configuration" error: "ERREUR" - keys: "Clés GPG" gpg_password: "Mot de passe GPG: " group: "Groupe" login: "Identifiant" @@ -185,7 +174,6 @@ fr: port: "Port" protocol: "Protocol" server: "Serveur" - wallets: "Porte-feuilles" warning: "Warning" formats: diff --git a/lib/mpw/cli.rb b/lib/mpw/cli.rb index e64d27c..722b16d 100644 --- a/lib/mpw/cli.rb +++ b/lib/mpw/cli.rb @@ -1,22 +1,20 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'readline' require 'locale' @@ -29,595 +27,521 @@ require 'mpw/item' require 'mpw/mpw' module MPW - class Cli - # @param config [Config] - def initialize(config) - @config = config - end - - # Change a parameter int the config after init - # @param options [Hash] param to change - def set_config(options) - @config.setup(options) - - puts I18n.t('form.set_config.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #15: #{e}".red - exit 2 - end - - # Change the wallet path - # @param path [String] new path - def set_wallet_path(path) - @config.set_wallet_path(path, @wallet) - - puts I18n.t('form.set_wallet_path.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #19: #{e}".red - exit 2 - end - - # Create a new config file - # @param options [Hash] - def setup(options) - options[:lang] = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] - - I18n.locale = options[:lang].to_sym - - @config.setup(options) - - load_config - - puts I18n.t('form.setup_config.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #8: #{e}".red - exit 2 - end - - # Setup a new GPG key - # @param gpg_key [String] gpg key name - def setup_gpg_key(gpg_key) - return if @config.check_gpg_key? - - password = ask(I18n.t('form.setup_gpg_key.password')) { |q| q.echo = false } - confirm = ask(I18n.t('form.setup_gpg_key.confirm_password')) { |q| q.echo = false } - - raise I18n.t('form.setup_gpg_key.error_password') if password != confirm - - @password = password.to_s - - puts I18n.t('form.setup_gpg_key.wait') - - @config.setup_gpg_key(@password, gpg_key) - - puts I18n.t('form.setup_gpg_key.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #8: #{e}".red - exit 2 - end - - # List gpg keys in wallet - def list_keys - table_list('keys', @mpw.list_keys) - end - - # List config - def list_config - config = { - 'lang' => @config.lang, - 'gpg_key' => @config.gpg_key, - 'default_wallet' => @config.default_wallet, - 'wallet_dir' => @config.wallet_dir, - 'pinmode' => @config.pinmode, - 'gpg_exe' => @config.gpg_exe - } - - @config.wallet_paths.each { |k, v| config["path_wallet_#{k}"] = "#{v}/#{k}.mpw" } - @config.password.each { |k, v| config["password_#{k}"] = v } - - table_list('config', config) - end - - # Load config - def load_config - @config.load_config - rescue => e - puts "#{I18n.t('display.error')} #10: #{e}".red - exit 2 - end - - # Request the GPG password and decrypt the file - def decrypt - if defined?(@mpw) - @mpw.read_data - else - begin - @mpw = MPW.new(@config.gpg_key, @wallet_file, nil, @config.gpg_exe, @config.pinmode) - - @mpw.read_data - rescue - @password = ask(I18n.t('display.gpg_password')) { |q| q.echo = false } - @mpw = MPW.new(@config.gpg_key, @wallet_file, @password, @config.gpg_exe, @config.pinmode) - - @mpw.read_data - end - end - rescue => e - puts "#{I18n.t('display.error')} #11: #{e}".red - exit 2 - end - - # Format list on a table - # @param title [String] name of table - # @param list an array or hash - def table_list(title, list) - length = { k: 0, v: 0 } - - if list.is_a?(Array) - i = 0 - list = list.map do |item| - i += 1 - [i, item] - end.to_h - end - - list.each do |k, v| - length[:k] = k.to_s.length if length[:k] < k.to_s.length - length[:v] = v.to_s.length if length[:v] < v.to_s.length - end - - puts "\n#{I18n.t("display.#{title}")}".red - print ' ' - (length[:k] + length[:v] + 5).times { print '=' } - print "\n" - - list.each do |k, v| - print " #{k}".cyan - (length[:k] - k.to_s.length + 1).times { print ' ' } - puts "| #{v}" - end - - print "\n" - end - - # Format items on a table - # @param items [Array] - def table_items(items = []) - group = '.' - i = 1 - length_total = 10 - data = { id: { length: 3, color: 'cyan' }, - host: { length: 9, color: 'yellow' }, - user: { length: 7, color: 'green' }, - otp: { length: 4, color: 'white' }, - comment: { length: 14, color: 'magenta' } } - - items.each do |item| - data.each do |k, v| - case k - when :id, :otp - next - when :host - v[:length] = item.url.length + 3 if item.url.length >= v[:length] - else - v[:length] = item.send(k.to_s).to_s.length + 3 if item.send(k.to_s).to_s.length >= v[:length] - end - end - end - data[:id][:length] = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length] - - data.each_value { |v| length_total += v[:length] } - items.sort! { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase } - - items.each do |item| - if group != item.group - group = item.group - - if group.to_s.empty? - puts "\n#{I18n.t('display.no_group')}".red - else - puts "\n#{group}".red - end - - print ' ' - length_total.times { print '=' } - print "\n " - data.each do |k, v| - case k - when :id - print ' ID' - when :otp - print '| OTP' - else - print "| #{k.to_s.capitalize}" - end - - (v[:length] - k.to_s.length).times { print ' ' } - end - print "\n " - length_total.times { print '=' } - print "\n" - end - - print " #{i}".send(data[:id][:color]) - (data[:id][:length] - i.to_s.length).times { print ' ' } - data.each do |k, v| - next if k == :id - - print '| ' - - case k - when :otp - item.otp ? (print ' X ') : 4.times { print ' ' } - - when :host - print "#{item.protocol}://".light_black if item.protocol - print item.host.send(v[:color]) - print ":#{item.port}".light_black if item.port - (v[:length] - item.url.to_s.length).times { print ' ' } - - else - print item.send(k.to_s).to_s.send(v[:color]) - (v[:length] - item.send(k.to_s).to_s.length).times { print ' ' } - end - end - print "\n" - - i += 1 - end - - print "\n" - end - - # Display the query's result - # @param options [Hash] the options to search - def list(**options) - result = @mpw.list(options) - - if result.empty? - puts I18n.t('display.nothing') - else - table_items(result) - end - end - - # Get an item when multiple choice - # @param items [Array] list of items - # @return [Item] an item - def get_item(items) - return items[0] if items.length == 1 - - items.sort! { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase } - choice = ask(I18n.t('form.select.choice')).to_i - - raise I18n.t('form.select.error') unless choice >= 1 && choice <= items.length - - items[choice - 1] - end - - # Print help message for clipboard mode - # @param item [Item] - def clipboard_help(item) - puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan - puts I18n.t('form.clipboard.help.url') - puts I18n.t('form.clipboard.help.login') - puts I18n.t('form.clipboard.help.password') - puts I18n.t('form.clipboard.help.otp_code') if item.otp - puts I18n.t('form.clipboard.help.quit') - end - - # Copy in clipboard the login and password - # @param item [Item] - # @param clipboard [Boolean] enable clipboard - def clipboard(item, clipboard = true) - # Security: force quit after 90s - Thread.new do - sleep 90 - exit - end - - Kernel.loop do - choice = ask(I18n.t('form.clipboard.choice')).to_s - - case choice - when 'q', 'quit' - break - - when 'u', 'url' - if clipboard - Clipboard.copy(item.url) - puts I18n.t('form.clipboard.url').green - else - puts item.url - end - - when 'l', 'login' - if clipboard - Clipboard.copy(item.user) - puts I18n.t('form.clipboard.login').green - else - puts item.user - end - - when 'p', 'password' - if clipboard - Clipboard.copy(@mpw.get_password(item.id)) - puts I18n.t('form.clipboard.password').yellow - - Thread.new do - sleep 30 - - Clipboard.clear - end - else - puts @mpw.get_password(item.id) - end - - when 'o', 'otp' - if !item.otp - clipboard_help(item) - next - elsif clipboard - Clipboard.copy(@mpw.get_otp_code(item.id)) - else - puts @mpw.get_otp_code(item.id) - end - puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow - - else - clipboard_help(item) - end - end - - Clipboard.clear - rescue SystemExit, Interrupt - Clipboard.clear - end - - # List all wallets - def list_wallet - wallets = @config.wallet_paths.keys - - Dir.glob("#{@config.wallet_dir}/*.mpw").each do |f| - wallet = File.basename(f, '.mpw') - wallet += ' *'.green if wallet == @config.default_wallet - wallets << wallet - end - - table_list('wallets', wallets) - end - - # Display the wallet - # @param wallet [String] wallet name - def get_wallet(wallet = nil) - @wallet = - if wallet.to_s.empty? - wallets = Dir.glob("#{@config.wallet_dir}/*.mpw") - if wallets.length == 1 - File.basename(wallets[0], '.mpw') - elsif !@config.default_wallet.to_s.empty? - @config.default_wallet - else - 'default' - end - else - wallet - end - - @wallet_file = - if @config.wallet_paths.key?(@wallet) - "#{@config.wallet_paths[@wallet]}/#{@wallet}.mpw" - else - "#{@config.wallet_dir}/#{@wallet}.mpw" - end - end - - # Add a new public key - # @param key [String] key name or key file to add - def add_key(key) - @mpw.add_key(key) - @mpw.write_data - - puts I18n.t('form.add_key.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #13: #{e}".red - end - - # Add new public key - # @param key [String] key name to delete - def delete_key(key) - @mpw.delete_key(key) - @mpw.write_data - - puts I18n.t('form.delete_key.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #15: #{e}".red - end - - # Text editor interface - # @param template_name [String] template name - # @param item [Item] the item to edit - # @param password [Boolean] disable field password - # @return [Hash] the values for an item - def text_editor(template_name, password = false, item = nil, **options) - editor = ENV['EDITOR'] || 'nano' - opts = {} - template_file = "#{File.expand_path('../../../templates', __FILE__)}/#{template_name}.erb" - template = ERB.new(IO.read(template_file)) - - Dir.mktmpdir do |dir| - tmp_file = "#{dir}/#{template_name}.yml" - - File.open(tmp_file, 'w') do |f| - f << template.result(binding) - end - - system("#{editor} #{tmp_file}") - - opts = YAML.load_file(tmp_file) - end - - opts.delete_if { |_, v| v.to_s.empty? } - - opts.each do |k, v| - options[k.to_sym] = v - end - - options - end - - # Form to add a new item - # @param password [Boolean] generate a random password - # @param text_editor [Boolean] enable text editor mode - # @param values [Hash] multiples value to set the item - def add(password = false, text_editor = false, **values) - options = text_editor('add_form', password, nil, values) if text_editor - item = Item.new(options) - options[:password] = MPW.password(@config.password) if password - - @mpw.add(item) - @mpw.set_password(item.id, options[:password]) if options.key?(:password) - @mpw.set_otp_key(item.id, options[:otp_key]) if options.key?(:otp_key) - @mpw.write_data - - puts I18n.t('form.add_item.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #13: #{e}".red - end - - # Update an item - # @param password [Boolean] generate a random password - # @param text_editor [Boolean] enable text editor mode - # @param options [Hash] the options to search - # @param values [Hash] multiples value to set the item - def update(password = false, text_editor = false, options = {}, **values) - items = @mpw.list(options) - - if items.empty? - puts I18n.t('display.nothing') - else - table_items(items) if items.length > 1 - - item = get_item(items) - values = text_editor('update_form', password, item, values) if text_editor - values[:password] = MPW.password(@config.password) if password - - item.update(values) - @mpw.set_password(item.id, values[:password]) if values.key?(:password) - @mpw.set_otp_key(item.id, values[:otp_key]) if values.key?(:otp_key) - @mpw.write_data - - puts I18n.t('form.update_item.valid').to_s.green - end - rescue => e - puts "#{I18n.t('display.error')} #14: #{e}".red - end - - # Remove an item - # @param options [Hash] the options to search - def delete(**options) - items = @mpw.list(options) - - if items.empty? - puts I18n.t('display.nothing') - else - table_items(items) - - item = get_item(items) - confirm = ask("#{I18n.t('form.delete_item.ask')} (y/N) ").to_s - - return unless confirm =~ /^(y|yes|YES|Yes|Y)$/ - - item.delete - @mpw.write_data - - puts I18n.t('form.delete_item.valid').to_s.green - end - rescue => e - puts "#{I18n.t('display.error')} #16: #{e}".red - end - - # Copy a password, otp, login - # @param clipboard [Boolean] enable clipboard - # @param options [Hash] the options to search - def copy(clipboard = true, **options) - items = @mpw.list(options) - - if items.empty? - puts I18n.t('display.nothing') - else - table_items(items) - - item = get_item(items) - clipboard(item, clipboard) - end - rescue => e - puts "#{I18n.t('display.error')} #14: #{e}".red - end - - # Export the items in an yaml file - # @param file [String] the path of destination file - # @param options [Hash] options to search - def export(file, options) - file = 'export-mpw.yml' if file.to_s.empty? - items = @mpw.list(options) - data = {} - - items.each do |item| - data.merge!( - item.id => { - 'comment' => item.comment, - 'created' => item.created, - 'group' => item.group, - 'last_edit' => item.last_edit, - 'otp_key' => @mpw.get_otp_key(item.id), - 'password' => @mpw.get_password(item.id), - 'url' => item.url, - 'user' => item.user - } - ) - end - - File.open(file, 'w') { |f| f << data.to_yaml } - - puts I18n.t('form.export.valid', file: file).to_s.green - rescue => e - puts "#{I18n.t('display.error')} #17: #{e}".red - end - - # Import items from an yaml file - # @param file [String] path of import file - # @param format [String] the software import file format - def import(file, format = 'mpw') - raise I18n.t('form.import.file_empty') if file.to_s.empty? - raise I18n.t('form.import.file_not_exist') unless File.exist?(file) - - begin - require "mpw/import/#{format}" - rescue LoadError - raise I18n.t('form.import.format_unknown', file_format: format) - end - - Import.send(format, file).each_value do |row| - item = Item.new( - comment: row['comment'], - group: row['group'], - url: row['url'], - user: row['user'] - ) - - next if item.empty? - - @mpw.add(item) - @mpw.set_password(item.id, row['password']) unless row['password'].to_s.empty? - @mpw.set_otp_key(item.id, row['otp_key']) unless row['otp_key'].to_s.empty? - end - - @mpw.write_data - - puts I18n.t('form.import.valid').to_s.green - rescue => e - puts "#{I18n.t('display.error')} #18: #{e}".red - end - end +class Cli + + # Constructor + # @args: config -> the config + # sync -> boolean for sync or not + def initialize(config, sync=true) + @config = config + @sync = sync + end + + # Change a parameter int the config after init + # @args: options -> param to change + def set_config(options) + gpg_key = options[:gpg_key] || @config.key + lang = options[:lang] || @config.lang + wallet_dir = options[:wallet_dir] || @config.wallet_dir + gpg_exe = options[:gpg_exe] || @config.gpg_exe + + @config.setup(gpg_key, lang, wallet_dir, gpg_exe) + rescue Exception => e + puts "#{I18n.t('display.error')} #15: #{e}".red + exit 2 + end + + # Create a new config file + # @args: options -> set param + def setup(options) + lang = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] + + I18n.locale = lang.to_sym + + @config.setup(options[:gpg_key], lang, options[:wallet_dir], options[:gpg_exe]) + + load_config + + puts "#{I18n.t('form.setup_config.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #8: #{e}".red + exit 2 + end + + # Setup a new GPG key + # @args: gpg_key -> the key name + def setup_gpg_key(gpg_key) + return if @config.check_gpg_key? + + password = ask(I18n.t('form.setup_gpg_key.password')) {|q| q.echo = false} + confirm = ask(I18n.t('form.setup_gpg_key.confirm_password')) {|q| q.echo = false} + + if password != confirm + raise I18n.t('form.setup_gpg_key.error_password') + end + + @password = password.to_s + + puts I18n.t('form.setup_gpg_key.wait') + + @config.setup_gpg_key(@password, gpg_key) + + puts "#{I18n.t('form.setup_gpg_key.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #8: #{e}".red + exit 2 + end + + # Setup wallet config for sync + # @args: options -> value to change + def setup_wallet_config(options={}) + if not options[:password].nil? + options[:password] = ask(I18n.t('form.setup_wallet.password')) {|q| q.echo = false} + end + + #wallet_file = wallet.nil? ? "#{@config.wallet_dir}/default.mpw" : "#{@config.wallet_dir}/#{wallet}.mpw" + + @mpw = MPW.new(@config.key, @wallet_file, @password, @config.gpg_exe) + @mpw.read_data + @mpw.set_config(options) + @mpw.write_data + + puts "#{I18n.t('form.setup_wallet.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #10: #{e}".red + exit 2 + end + + # Load config + def load_config + @config.load_config + + rescue Exception => e + puts "#{I18n.t('display.error')} #10: #{e}".red + exit 2 + end + + # Request the GPG password and decrypt the file + def decrypt + if not defined?(@mpw) + @password = ask(I18n.t('display.gpg_password')) {|q| q.echo = false} + @mpw = MPW.new(@config.key, @wallet_file, @password, @config.gpg_exe) + end + + @mpw.read_data + @mpw.sync if @sync + rescue Exception => e + puts "#{I18n.t('display.error')} #11: #{e}".red + exit 2 + end + + # Format items on a table + def table(items=[]) + group = '.' + i = 1 + length_total = 10 + data = { id: { length: 3, color: 'cyan' }, + host: { length: 9, color: 'yellow' }, + user: { length: 7, color: 'green' }, + protocol: { length: 9, color: 'white' }, + port: { length: 5, color: 'white' }, + otp: { length: 4, color: 'white' }, + comment: { length: 14, color: 'magenta' }, + } + + items.each do |item| + data.each do |k, v| + next if k == :id or k == :otp + + v[:length] = item.send(k.to_s).length + 3 if item.send(k.to_s).to_s.length >= v[:length] + end + end + data[:id][:length] = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length] + + data.each_value { |v| length_total += v[:length] } + items.sort! { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase } + + items.each do |item| + if group != item.group + group = item.group + + if group.to_s.empty? + puts "\n#{I18n.t('display.no_group')}".red + else + puts "\n#{group}".red + end + + print ' ' + length_total.times { print '=' } + print "\n " + data.each do |k, v| + case k + when :id + print ' ID' + when :otp + print '| OTP' + else + print "| #{k.to_s.capitalize}" + end + + (v[:length] - k.to_s.length).times { print ' ' } + end + print "\n " + length_total.times { print '=' } + print "\n" + end + + print " #{i}".send(data[:id][:color]) + (data[:id][:length] - i.to_s.length).times { print ' ' } + data.each do |k, v| + next if k == :id + + if k == :otp + print '| ' + if item.otp; print ' X ' else 4.times { print ' ' } end + + next + end + + print '| ' + print "#{item.send(k.to_s)}".send(v[:color]) + (v[:length] - item.send(k.to_s).to_s.length).times { print ' ' } + end + print "\n" + + i += 1 + end + + print "\n" + end + + # Display the query's result + # @args: options -> the option to search + def list(options={}) + result = @mpw.list(options) + + if result.length == 0 + puts I18n.t('display.nothing') + + else + table(result) + end + end + + # Get an item when multiple choice + # @args: items -> array of items + # @rtrn: item + def get_item(items) + return items[0] if items.length == 1 + + items.sort! { |a,b| a.group.to_s.downcase <=> b.group.to_s.downcase } + choice = ask(I18n.t('form.select')).to_i + + if choice >= 1 and choice <= items.length + return items[choice-1] + else + return nil + end + end + + # Copy in clipboard the login and password + # @args: item -> the item + # clipboard -> enable clipboard + def clipboard(item, clipboard=true) + pid = nil + + # Security: force quit after 90s + Thread.new do + sleep 90 + exit + end + + while true + choice = ask(I18n.t('form.clipboard.choice')).to_s + + case choice + when 'q', 'quit' + break + + when 'l', 'login' + if clipboard + Clipboard.copy(item.user) + puts I18n.t('form.clipboard.login').green + else + puts item.user + end + + when 'p', 'password' + if clipboard + Clipboard.copy(@mpw.get_password(item.id)) + puts I18n.t('form.clipboard.password').yellow + + Thread.new do + sleep 30 + + Clipboard.clear + end + else + puts @mpw.get_password(item.id) + end + + when 'o', 'otp' + if clipboard + Clipboard.copy(@mpw.get_otp_code(item.id)) + else + puts @mpw.get_otp_code(item.id) + end + puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow + + else + puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan + puts I18n.t('form.clipboard.help.login') + puts I18n.t('form.clipboard.help.password') + puts I18n.t('form.clipboard.help.otp_code') + puts I18n.t('form.clipboard.help.quit') + next + end + end + + Clipboard.clear + rescue SystemExit, Interrupt + Clipboard.clear + end + + # List all wallets + def list_wallet + wallets = Dir.glob("#{@config.wallet_dir}/*.mpw") + + wallets.each do |wallet| + puts File.basename(wallet, '.mpw') + end + end + + # Display the wallet + # @args: wallet -> the wallet name + def get_wallet(wallet=nil) + if wallet.to_s.empty? + wallets = Dir.glob("#{@config.wallet_dir}/*.mpw") + + if wallets.length == 1 + @wallet_file = wallets[0] + else + @wallet_file = "#{@config.wallet_dir}/default.mpw" + end + else + @wallet_file = "#{@config.wallet_dir}/#{wallet}.mpw" + end + end + + # Add a new public key + # args: key -> the key name to add + # file -> gpg public file to import + def add_key(key, file=nil) + @mpw.add_key(key, file) + @mpw.write_data + @mpw.sync(true) if @sync + + puts "#{I18n.t('form.add_key.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #13: #{e}".red + end + + # Add new public key + # args: key -> the key name to delete + def delete_key(key) + @mpw.delete_key(key) + @mpw.write_data + @mpw.sync(true) if @sync + + puts "#{I18n.t('form.delete_key.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #15: #{e}".red + end + + # Text editor interface + # @args: template -> template name + # item -> the item to edit + # password -> disable field password + def text_editor(template_name, item=nil, password=false) + editor = ENV['EDITOR'] || 'nano' + options = {} + opts = {} + template_file = "#{File.expand_path('../../../templates', __FILE__)}/#{template_name}.erb" + template = ERB.new(IO.read(template_file)) + + Dir.mktmpdir do |dir| + tmp_file = "#{dir}/#{template_name}.yml" + + File.open(tmp_file, 'w') do |f| + f << template.result(binding) + end + + system("#{editor} #{tmp_file}") + + opts = YAML::load_file(tmp_file) + end + + opts.delete_if { |k,v| v.to_s.empty? } + + opts.each do |k,v| + options[k.to_sym] = v + end + + return options + end + + # Form to add a new item + # @args: password -> generate a random password + def add(password=false) + options = text_editor('add_form', nil, password) + item = Item.new(options) + + if password + options[:password] = MPW::password(length: 24) + end + + @mpw.add(item) + @mpw.set_password(item.id, options[:password]) if options.has_key?(:password) + @mpw.set_otp_key(item.id, options[:otp_key]) if options.has_key?(:otp_key) + @mpw.write_data + @mpw.sync(true) if @sync + + puts "#{I18n.t('form.add_item.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #13: #{e}".red + end + + # Update an item + # @args: options -> the option to search + def update(options={}) + items = @mpw.list(options) + + if items.length == 0 + puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow + else + table(items) if items.length > 1 + + item = get_item(items) + options = text_editor('update_form', item) + + item.update(options) + @mpw.set_password(item.id, options[:password]) if options.has_key?(:password) + @mpw.set_otp_key(item.id, options[:otp_key]) if options.has_key?(:otp_key) + @mpw.write_data + @mpw.sync(true) if @sync + + puts "#{I18n.t('form.update_item.valid')}".green + end + rescue Exception => e + puts "#{I18n.t('display.error')} #14: #{e}".red + end + + # Remove an item + # @args: options -> the option to search + def delete(options={}) + items = @mpw.list(options) + + if items.length == 0 + puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow + else + table(items) + + item = get_item(items) + confirm = ask("#{I18n.t('form.delete_item.ask')} (y/N) ").to_s + + if not confirm =~ /^(y|yes|YES|Yes|Y)$/ + return false + end + + item.delete + @mpw.write_data + @mpw.sync(true) if @sync + + puts "#{I18n.t('form.delete_item.valid')}".green + end + rescue Exception => e + puts "#{I18n.t('display.error')} #16: #{e}".red + end + + # Copy a password, otp, login + # @args: clipboard -> enable clipboard + # options -> the option to search + def copy(clipboard=true, options={}) + items = @mpw.list(options) + + if items.length == 0 + puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow + else + table(items) + + item = get_item(items) + clipboard(item, clipboard) + end + rescue Exception => e + puts "#{I18n.t('display.error')} #14: #{e}".red + end + + # Export the items in a CSV file + # @args: file -> the destination file + # options -> option to search + def export(file, options) + file = 'export-mpw.yml' if file.to_s.empty? + items = @mpw.list(options) + data = {} + i = 1 + + items.each do |item| + data.merge!(i => { 'host' => item.host, + 'user' => item.user, + 'group' => item.group, + 'password' => @mpw.get_password(item.id), + 'protocol' => item.protocol, + 'port' => item.port, + 'otp_key' => @mpw.get_otp_key(item.id), + 'comment' => item.comment, + 'last_edit' => item.last_edit, + 'created' => item.created, + } + ) + + i += 1 + end + + File.open(file, 'w') {|f| f << data.to_yaml} + + puts "#{I18n.t('export.valid', file)}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #17: #{e}".red + end + + # Import items from a YAML file + # @args: file -> the import file + def import(file) + raise I18n.t('import.file_empty') if file.to_s.empty? + raise I18n.t('import.file_not_exist') if not File.exist?(file) + + YAML::load_file(file).each_value do |row| + + item = Item.new(group: row['group'], + host: row['host'], + protocol: row['protocol'], + user: row['user'], + port: row['port'], + comment: row['comment'], + ) + + next if item.empty? + + @mpw.add(item) + @mpw.set_password(item.id, row['password']) if not row['password'].to_s.empty? + @mpw.set_otp_key(item.id, row['otp_key']) if not row['otp_key'].to_s.empty? + end + + @mpw.write_data + + puts "#{I18n.t('form.import.valid')}".green + rescue Exception => e + puts "#{I18n.t('display.error')} #18: #{e}".red + end +end end diff --git a/lib/mpw/config.rb b/lib/mpw/config.rb index f3974b5..e8911fa 100644 --- a/lib/mpw/config.rb +++ b/lib/mpw/config.rb @@ -1,190 +1,144 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'gpgme' require 'yaml' require 'i18n' require 'fileutils' - + module MPW - class Config - attr_accessor :error_msg +class Config + + attr_accessor :error_msg - attr_accessor :gpg_key - attr_accessor :lang - attr_accessor :config_dir - attr_accessor :default_wallet - attr_accessor :wallet_dir - attr_accessor :wallet_paths - attr_accessor :gpg_exe - attr_accessor :password - attr_accessor :pinmode + attr_accessor :key + attr_accessor :lang + attr_accessor :config_dir + attr_accessor :wallet_dir + attr_accessor :gpg_exe - # @param config_file [String] path of config file - def initialize(config_file = nil) - @config_file = config_file - @config_dir = - if RUBY_PLATFORM =~ /darwin/ - "#{Dir.home}/Library/Preferences/mpw" - elsif RUBY_PLATFORM =~ /cygwin|mswin|mingw|bccwin|wince|emx/ - "#{Dir.home}/AppData/Local/mpw" - else - "#{Dir.home}/.config/mpw" - end + # Constructor + # @args: config_file -> the specify config file + def initialize(config_file=nil) + @config_file = config_file - @config_file = "#{@config_dir}/mpw.cfg" if @config_file.to_s.empty? - end + if /darwin/ =~ RUBY_PLATFORM + @config_dir = "#{Dir.home}/Library/Preferences/mpw" + elsif /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM + @config_dir = "#{Dir.home}/AppData/Local/mpw" + else + @config_dir = "#{Dir.home}/.config/mpw" + end + + if @config_file.nil? or @config_file.empty? + @config_file = "#{@config_dir}/mpw.cfg" + end + end - # Create a new config file - # @param options [Hash] the value to set the config file - def setup(**options) - gpg_key = options[:gpg_key] || @gpg_key - lang = options[:lang] || @lang - wallet_dir = options[:wallet_dir] || @wallet_dir - default_wallet = options[:default_wallet] || @default_wallet - gpg_exe = options[:gpg_exe] || @gpg_exe - pinmode = options.key?(:pinmode) ? options[:pinmode] : @pinmode - password = { - numeric: true, - alpha: true, - special: false, - length: 16 - } + # Create a new config file + # @args: key -> the gpg key to encrypt + # lang -> the software language + # wallet_dir -> the directory where are the wallets password + # gpg_exe -> the path of gpg executable + # @rtrn: true if le config file is create + def setup(key, lang, wallet_dir, gpg_exe) + if not key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/ + raise I18n.t('error.config.key_bad_format') + end - %w[numeric special alpha length].each do |k| - if options.key?("pwd_#{k}".to_sym) - password[k.to_sym] = options["pwd_#{k}".to_sym] - elsif !@password.nil? && @password.key?(k.to_sym) - password[k.to_sym] = @password[k.to_sym] - end - end + if wallet_dir.to_s.empty? + wallet_dir = "#{@config_dir}/wallets" + end - unless gpg_key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/ - raise I18n.t('error.config.key_bad_format') - end + config = { 'key' => key, + 'lang' => lang, + 'wallet_dir' => wallet_dir, + 'gpg_exe' => gpg_exe, + } - wallet_dir = "#{@config_dir}/wallets" if wallet_dir.to_s.empty? - config = { 'gpg_key' => gpg_key, - 'lang' => lang, - 'wallet_dir' => wallet_dir, - 'default_wallet' => default_wallet, - 'gpg_exe' => gpg_exe, - 'password' => password, - 'pinmode' => pinmode, - 'wallet_paths' => @wallet_paths } + FileUtils.mkdir_p(@config_dir, mode: 0700) + FileUtils.mkdir_p(wallet_dir, mode: 0700) - FileUtils.mkdir_p(@config_dir, mode: 0700) - FileUtils.mkdir_p(wallet_dir, mode: 0700) + File.open(@config_file, 'w') do |file| + file << config.to_yaml + end + + rescue Exception => e + raise "#{I18n.t('error.config.write')}\n#{e}" + end - File.open(@config_file, 'w') do |file| - file << config.to_yaml - end - rescue => e - raise "#{I18n.t('error.config.write')}\n#{e}" - end + # Setup a new gpg key + # @args: password -> the GPG key password + # name -> the name of user + # length -> length of the GPG key + # expire -> the time of expire to GPG key + # @rtrn: true if the GPG key is create, else false + def setup_gpg_key(password, name, length = 4096, expire = 0) + if name.to_s.empty? + raise "#{I18n.t('error.config.genkey_gpg.name')}" + elsif password.to_s.empty? + raise "#{I18n.t('error.config.genkey_gpg.password')}" + end - # Setup a new gpg key - # @param password [String] gpg key password - # @param name [String] the name of user - # @param length [Integer] length of the gpg key - # @param expire [Integer] time of expire to gpg key - def setup_gpg_key(password, name, length = 4096, expire = 0) - raise I18n.t('error.config.genkey_gpg.name') if name.to_s.empty? - raise I18n.t('error.config.genkey_gpg.password') if password.to_s.empty? + param = '' + param << '<GnupgKeyParms format="internal">' + "\n" + param << "Key-Type: DSA\n" + param << "Key-Length: #{length}\n" + param << "Subkey-Type: ELG-E\n" + param << "Subkey-Length: #{length}\n" + param << "Name-Real: #{name}\n" + param << "Name-Comment: #{name}\n" + param << "Name-Email: #{@key}\n" + param << "Expire-Date: #{expire}\n" + param << "Passphrase: #{password}\n" + param << "</GnupgKeyParms>\n" - param = '' - param << '<GnupgKeyParms format="internal">' + "\n" - param << "Key-Type: RSA\n" - param << "Key-Length: #{length}\n" - param << "Subkey-Type: ELG-E\n" - param << "Subkey-Length: #{length}\n" - param << "Name-Real: #{name}\n" - param << "Name-Comment: #{name}\n" - param << "Name-Email: #{@gpg_key}\n" - param << "Expire-Date: #{expire}\n" - param << "Passphrase: #{password}\n" - param << "</GnupgKeyParms>\n" + ctx = GPGME::Ctx.new + ctx.genkey(param, nil, nil) + rescue Exception => e + raise "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}" + end - ctx = GPGME::Ctx.new - ctx.genkey(param, nil, nil) - rescue => e - raise "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}" - end + # Load the config file + def load_config + config = YAML::load_file(@config_file) + @key = config['key'] + @lang = config['lang'] + @wallet_dir = config['wallet_dir'] + @gpg_exe = config['gpg_exe'] - # Load the config file - def load_config - config = YAML.load_file(@config_file) - @gpg_key = config['gpg_key'] - @lang = config['lang'] - @wallet_dir = config['wallet_dir'] - @wallet_paths = config['wallet_paths'] || {} - @default_wallet = config['default_wallet'] - @gpg_exe = config['gpg_exe'] - @password = config['password'] || {} - @pinmode = config['pinmode'] || false + raise if @key.empty? or @wallet_dir.empty? + + I18n.locale = @lang.to_sym - raise if @gpg_key.empty? || @wallet_dir.empty? + rescue Exception => e + raise "#{I18n.t('error.config.load')}\n#{e}" + end - I18n.locale = @lang.to_sym - rescue => e - raise "#{I18n.t('error.config.load')}\n#{e}" - end + # Check if private key exist + # @rtrn: true if the key exist, else false + def check_gpg_key? + ctx = GPGME::Ctx.new + ctx.each_key(@key, true) do + return true + end - # Check if private key exist - # @return [Boolean] true if the key exist, else false - def check_gpg_key? - ctx = GPGME::Ctx.new - ctx.each_key(@gpg_key, true) do - return true - end - - false - end - - # Change the path of one wallet - # @param path [String]new directory path - # @param wallet [String] wallet name - def set_wallet_path(path, wallet) - path = @wallet_dir if path == 'default' - path = File.absolute_path(path) - - return if path == @wallet_dir && File.exist?("#{@wallet_dir}/#{wallet}.mpw") - return if path == @wallet_paths[wallet] - - old_wallet_file = - if @wallet_paths.key?(wallet) - "#{@wallet_paths[wallet]}/#{wallet}.mpw" - else - "#{@wallet_dir}/#{wallet}.mpw" - end - - FileUtils.mkdir_p(path) unless Dir.exist?(path) - FileUtils.mv(old_wallet_file, "#{path}/#{wallet}.mpw") if File.exist?(old_wallet_file) - - if path == @wallet_dir - @wallet_paths.delete(wallet) - else - @wallet_paths[wallet] = path - end - - setup - end - end + return false + end +end end diff --git a/lib/mpw/import/gorilla.rb b/lib/mpw/import/gorilla.rb deleted file mode 100644 index feffce6..0000000 --- a/lib/mpw/import/gorilla.rb +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 'csv' - -module MPW - module Import - # Import an export mpw file - # @param file [String] the file path to import - def self.gorilla(file) - data = {} - - CSV.foreach(file, headers: true) do |row| - id = row['uuid'] - comment = - if row['title'] && row['notes'] - "#{row['title']} #{row['notes']}" - elsif row['title'] - row['title'] - elsif row['notes'] - row['notes'] - end - - data[id] = { - 'comment' => comment, - 'group' => row['group'], - 'password' => row['password'], - 'url' => row['url'], - 'user' => row['user'] - } - end - - data - end - end -end diff --git a/lib/mpw/import/keepass.rb b/lib/mpw/import/keepass.rb deleted file mode 100644 index 0b961d4..0000000 --- a/lib/mpw/import/keepass.rb +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 'csv' - -module MPW - module Import - # Import an keepass2 export csv file - # @param file [String] the file path to import - def self.keepass(file) - data = {} - - CSV.foreach(file, headers: true) do |row| - id = "#{row['Group']} #{row['Title']}" - comment = - if row['Title'] && row['Notes'] - "#{row['Title']} #{row['Notes']}" - elsif row['Title'] - row['Title'] - elsif row['Notes'] - row['Notes'] - end - - data[id] = { - 'comment' => comment, - 'group' => row['Group'], - 'password' => row['Password'], - 'url' => row['URL'], - 'user' => row['Username'] - } - end - - data - end - end -end diff --git a/lib/mpw/import/mpw.rb b/lib/mpw/import/mpw.rb deleted file mode 100644 index a287357..0000000 --- a/lib/mpw/import/mpw.rb +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 'yaml' - -module MPW - module Import - # Import an export mpw file - # @param file [String] the file path to import - def self.mpw(file) - YAML.load_file(file) - end - end -end diff --git a/lib/mpw/import/mpw_old.rb b/lib/mpw/import/mpw_old.rb deleted file mode 100644 index 923bf16..0000000 --- a/lib/mpw/import/mpw_old.rb +++ /dev/null @@ -1,48 +0,0 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 'yaml' - -module MPW - module Import - # Import an export mpw file - # @param file [String] the file path to import - def self.mpw_old(file) - data = {} - YAML.load_file(file).each do |id, item| - url = '' - url += "#{item['protocol']}://" if item['protocol'] - url += item['host'] - url += ":#{item['port']}" if item['port'] - - data[id] = { - 'comment' => item['comment'], - 'group' => item['group'], - 'otp' => item['otp'], - 'password' => item['password'], - 'url' => url, - 'user' => item['user'] - } - end - - data - end - end -end diff --git a/lib/mpw/item.rb b/lib/mpw/item.rb index 9da60b1..4beb49f 100644 --- a/lib/mpw/item.rb +++ b/lib/mpw/item.rb @@ -1,108 +1,109 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'i18n' -require 'uri' - + module MPW - class Item - attr_accessor :created - attr_accessor :comment - attr_accessor :group - attr_accessor :host - attr_accessor :id - attr_accessor :otp - attr_accessor :port - attr_accessor :protocol - attr_accessor :last_edit - attr_accessor :url - attr_accessor :user +class Item - # @param options [Hash] the option :host is required - def initialize(**options) - @host = '' + attr_accessor :id + attr_accessor :group + attr_accessor :host + attr_accessor :protocol + attr_accessor :user + attr_accessor :port + attr_accessor :otp + attr_accessor :comment + attr_accessor :last_edit + attr_accessor :last_sync + attr_accessor :created - if !options[:id] || !options[:created] - @id = generate_id - @created = Time.now.to_i - else - @id = options[:id] - @created = options[:created] - @last_edit = options[:last_edit] - options[:no_update_last_edit] = true - end + # Constructor + # Create a new item + # @args: options -> a hash of parameter + # raise an error if the hash hasn't the key name + def initialize(options={}) + if not options.has_key?(:host) or options[:host].to_s.empty? + raise I18n.t('error.update.name_empty') + end - update(options) - end + if not options.has_key?(:id) or options[:id].to_s.empty? or not options.has_key?(:created) or options[:created].to_s.empty? + @id = generate_id + @created = Time.now.to_i + else + @id = options[:id] + @created = options[:created] + @last_edit = options[:last_edit] + options[:no_update_last_edit] = true + end - # Update the item - # @param options [Hash] - def update(**options) - unless options[:url] || options[:comment] - raise I18n.t('error.update.host_and_comment_empty') - end + update(options) + end - if options[:url] - uri = URI(options[:url]) - @host = uri.host || options[:url] - @port = uri.port || nil - @protocol = uri.scheme || nil - @url = options[:url] - end + # Update the item + # @args: options -> a hash of parameter + def update(options={}) + if options.has_key?(:host) and options[:host].to_s.empty? + raise I18n.t('error.update.name_empty') + end - @comment = options[:comment] if options.key?(:comment) - @group = options[:group] if options.key?(:group) - @last_edit = Time.now.to_i unless options.key?(:no_update_last_edit) - @otp = options[:otp] if options.key?(:otp) - @user = options[:user] if options.key?(:user) - end + @group = options[:group] if options.has_key?(:group) + @host = options[:host] if options.has_key?(:host) + @protocol = options[:protocol] if options.has_key?(:protocol) + @user = options[:user] if options.has_key?(:user) + @port = options[:port].to_i if options.has_key?(:port) and not options[:port].to_s.empty? + @otp = options[:otp] if options.has_key?(:otp) + @comment = options[:comment] if options.has_key?(:comment) + @last_edit = Time.now.to_i if not options.has_key?(:no_update_last_edit) + end - # Delete all data - def delete - @id = nil - @comment = nil - @created = nil - @group = nil - @host = nil - @last_edit = nil - @otp = nil - @port = nil - @protocol = nil - @url = nil - @user = nil - end + # Update last_sync + def set_last_sync + @last_sync = Time.now.to_i + end - def empty? - @id.to_s.empty? - end + # Delete all data + def delete + @id = nil + @group = nil + @host = nil + @protocol = nil + @user = nil + @port = nil + @otp = nil + @comment = nil + @created = nil + @last_edit = nil + @last_sync = nil + end - def nil? - false - end + def empty? + return @id.to_s.empty? + end - private + def nil? + return false + end - # Generate an random id - # @return [String] random string - def generate_id - [*('A'..'Z'), *('a'..'z'), *('0'..'9')].sample(16).join - end - end + # Generate an random id + private + def generate_id + return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(16).join + end +end end diff --git a/lib/mpw/mpw.rb b/lib/mpw/mpw.rb index 191caae..533077e 100644 --- a/lib/mpw/mpw.rb +++ b/lib/mpw/mpw.rb @@ -1,22 +1,20 @@ -# -# Copyright:: 2013, Adrien Waksberg -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'rubygems/package' require 'gpgme' @@ -24,328 +22,454 @@ require 'i18n' require 'yaml' require 'rotp' require 'mpw/item' - + module MPW - class MPW - # @param key [String] gpg key name - # @param wallet_file [String] path of the wallet file - # @param gpg_pass [String] password of the gpg key - # @param gpg_exe [String] path of the gpg executable - # @param pinmode [Boolean] enable the gpg pinmode - def initialize(key, wallet_file, gpg_pass = nil, gpg_exe = nil, pinmode = false) - @key = key - @gpg_pass = gpg_pass - @gpg_exe = gpg_exe - @wallet_file = wallet_file - @pinmode = pinmode +class MPW - GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg") unless @gpg_exe.to_s.empty? - end + # Constructor + def initialize(key, wallet_file, gpg_pass=nil, gpg_exe=nil) + @key = key + @gpg_pass = gpg_pass + @gpg_exe = gpg_exe + @wallet_file = wallet_file - # Read mpw file - def read_data - @data = [] - @keys = {} - @passwords = {} - @otp_keys = {} + if not @gpg_exe.to_s.empty? + GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg") + end + end - data = nil + # Read mpw file + def read_data + @config = {} + @data = [] + @keys = {} + @passwords = {} + @otp_keys = {} - return unless File.exist?(@wallet_file) + data = nil - Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar| - tar.each do |f| - case f.full_name - when 'wallet/meta.gpg' - data = decrypt(f.read) + return if not File.exists?(@wallet_file) - when %r{^wallet/keys/(?<key>.+)\.pub$} - key = Regexp.last_match('key') + Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar| + tar.each do |f| + case f.full_name + when 'wallet/config.gpg' + @config = YAML.load(decrypt(f.read)) - if GPGME::Key.find(:public, key).empty? - GPGME::Key.import(f.read, armor: true) - end + when 'wallet/meta.gpg' + data = decrypt(f.read) - @keys[key] = f.read + when /^wallet\/keys\/(?<key>.+)\.pub$/ + key = Regexp.last_match('key') - when %r{^wallet/passwords/(?<id>[a-zA-Z0-9]+)\.gpg$} - @passwords[Regexp.last_match('id')] = f.read + if GPGME::Key.find(:public, key).length == 0 + GPGME::Key.import(f.read, armor: true) + end - when %r{^wallet/otp_keys/(?<id>[a-zA-Z0-9]+)\.gpg$} - @otp_keys[Regexp.last_match('id')] = f.read + @keys[key] = f.read - else - next - end - end - end + when /^wallet\/passwords\/(?<id>[a-zA-Z0-9]+)\.gpg$/ + @passwords[Regexp.last_match('id')] = f.read - unless data.to_s.empty? - YAML.safe_load(data).each_value do |d| - @data.push( - Item.new( - id: d['id'], - group: d['group'], - user: d['user'], - url: d['url'], - otp: @otp_keys.key?(d['id']), - comment: d['comment'], - last_edit: d['last_edit'], - created: d['created'] - ) - ) - end - end + when /^wallet\/otp_keys\/(?<id>[a-zA-Z0-9]+)\.gpg$/ + @otp_keys[Regexp.last_match('id')] = f.read - add_key(@key) unless @keys.key?(@key) - rescue => e - raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}" - end + else + next + end + end + end - # Encrypt all data in tarball - def write_data - data = {} - tmp_file = "#{@wallet_file}.tmp" + if not data.nil? and not data.empty? + YAML.load(data).each_value do |d| + @data.push(Item.new(id: d['id'], + group: d['group'], + host: d['host'], + protocol: d['protocol'], + user: d['user'], + port: d['port'], + otp: @otp_keys.has_key?(d['id']), + comment: d['comment'], + last_edit: d['last_edit'], + created: d['created'], + ) + ) + end + end - @data.each do |item| - next if item.empty? + add_key(@key) if @keys[@key].nil? + rescue Exception => e + raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}" + end - data.merge!( - item.id => { - 'id' => item.id, - 'group' => item.group, - 'user' => item.user, - 'url' => item.url, - 'comment' => item.comment, - 'last_edit' => item.last_edit, - 'created' => item.created - } - ) - end + # Encrypt a file + def write_data + data = {} + tmp_file = "#{@wallet_file}.tmp" - Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar| - data_encrypt = encrypt(data.to_yaml) - tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io| - io.write(data_encrypt) - end + @data.each do |item| + next if item.empty? - @passwords.each do |id, password| - tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io| - io.write(password) - end - end + data.merge!(item.id => { 'id' => item.id, + 'group' => item.group, + 'host' => item.host, + 'protocol' => item.protocol, + 'user' => item.user, + 'port' => item.port, + 'comment' => item.comment, + 'last_edit' => item.last_edit, + 'created' => item.created, + } + ) + end - @otp_keys.each do |id, key| - tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io| - io.write(key) - end - end + @config['last_update'] = Time.now.to_i - @keys.each do |id, key| - tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io| - io.write(key) - end - end - end + Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar| + data_encrypt = encrypt(data.to_yaml) + tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io| + io.write(data_encrypt) + end - File.rename(tmp_file, @wallet_file) - rescue => e - File.unlink(tmp_file) if File.exist?(tmp_file) + config = encrypt(@config.to_yaml) + tar.add_file_simple('wallet/config.gpg', 0400, config.length) do |io| + io.write(config) + end - raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}" - end + @passwords.each do |id, password| + tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io| + io.write(password) + end + end - # Get a password - # @param id [String] the item id - def get_password(id) - password = decrypt(@passwords[id]) + @otp_keys.each do |id, key| + tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io| + io.write(key) + end + end - if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password - Regexp.last_match('password') - else - password - end - end + @keys.each do |id, key| + tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io| + io.write(key) + end + end + end - # Set a new password for an item - # @param id [String] the item id - # @param password [String] the new password - def set_password(id, password) - salt = MPW.password(length: Random.rand(4..9)) - password = "$#{salt}::#{password}" + File.rename(tmp_file, @wallet_file) + rescue Exception => e + File.unlink(tmp_file) if File.exist?(tmp_file) - @passwords[id] = encrypt(password) - end + raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}" + end - # Return the list of all gpg keys - # @return [Array] the gpg keys name - def list_keys - @keys.keys - end + # Get a password + # args: id -> the item id + def get_password(id) + password = decrypt(@passwords[id]) + + if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password + return Regexp.last_match('password') + else + return password + end + end - # Add a public key - # @param key [String] new public key file or name - def add_key(key) - if File.exist?(key) - data = File.open(key).read - key_import = GPGME::Key.import(data, armor: true) - key = GPGME::Key.get(key_import.imports[0].fpr).uids[0].email - else - data = GPGME::Key.export(key, armor: true).read - end + # Set a password + # args: id -> the item id + # password -> the new password + def set_password(id, password) + salt = MPW::password(length: Random.rand(4..9)) + password = "$#{salt}::#{password}" - raise I18n.t('error.export_key') if data.to_s.empty? + @passwords[id] = encrypt(password) + end - @keys[key] = data - @passwords.each_key { |id| set_password(id, get_password(id)) } - @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) } - end + # Add a public key + # args: key -> new public key + # file -> public gpg file to import + def add_key(key, file=nil) + if not file.nil? and File.exists?(file) + data = File.open(file).read + GPGME::Key.import(data, armor: true) + else + data = GPGME::Key.export(key, armor: true).read + end - # Delete a public key - # @param key [String] public key to delete - def delete_key(key) - @keys.delete(key) - @passwords.each_key { |id| set_password(id, get_password(id)) } - @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) } - end + if data.to_s.empty? + raise I18n.t('error.export_key') + end - # Add a new item - # @param item [Item] - def add(item) - raise I18n.t('error.bad_class') unless item.instance_of?(Item) - raise I18n.t('error.empty') if item.empty? + @keys[key] = data + end - @data.push(item) - end + # Delete a public key + # args: key -> public key to delete + def delete_key(key) + @keys.delete(key) + end - # Search in some csv data - # @param options [Hash] - # @return [Array] a list with the resultat of the search - def list(**options) - result = [] + # Set config + # args: config -> a hash with config options + def set_config(options={}) + @config = {} if @config.nil? - search = options[:pattern].to_s.downcase - group = options[:group].to_s.downcase + @config['protocol'] = options[:protocol] if options.has_key?(:protocol) + @config['host'] = options[:host] if options.has_key?(:host) + @config['port'] = options[:port] if options.has_key?(:port) + @config['user'] = options[:user] if options.has_key?(:user) + @config['password'] = options[:password] if options.has_key?(:password) + @config['path'] = options[:path] if options.has_key?(:path) + @config['last_sync'] = @config['last_sync'].nil? ? 0 : @config['last_sync'] + end - @data.each do |item| - next if item.empty? - next unless group.empty? || group.eql?(item.group.to_s.downcase) + # Add a new item + # @args: item -> Object MPW::Item + def add(item) + if not item.instance_of?(Item) + raise I18n.t('error.bad_class') + elsif item.empty? + raise I18n.t('error.add.empty') + else + @data.push(item) + end + end - host = item.host.to_s.downcase - comment = item.comment.to_s.downcase + # Search in some csv data + # @args: options -> a hash with paramaters + # @rtrn: a list with the resultat of the search + def list(options={}) + result = [] - next unless host =~ /^.*#{search}.*$/ || comment =~ /^.*#{search}.*$/ + search = options[:pattern].to_s.downcase + group = options[:group].to_s.downcase - result.push(item) - end + @data.each do |item| + next if item.empty? + next if not group.empty? and not group.eql?(item.group.to_s.downcase) + + host = item.host.to_s.downcase + comment = item.comment.to_s.downcase - result - end + if not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/ + next + end - # Search an item with an id - # @param id [String]the id item - # @return [Item] an item or nil - def search_by_id(id) - @data.each do |item| - return item if item.id == id - end + result.push(item) + end - nil - end + return result + end - # Set a new opt key - # @param id [String] the item id - # @param key [String] the new key - def set_otp_key(id, key) - @otp_keys[id] = encrypt(key.to_s) unless key.to_s.empty? - end + # Search in some csv data + # @args: id -> the id item + # @rtrn: a row with the result of the search + def search_by_id(id) + @data.each do |item| + return item if item.id == id + end - # Get an opt key - # @param id [String] the item id - def get_otp_key(id) - @otp_keys.key?(id) ? decrypt(@otp_keys[id]) : nil - end + return nil + end - # Get an otp code - # @param id [String] the item id - # @return [String] an otp code - def get_otp_code(id) - @otp_keys.key?(id) ? ROTP::TOTP.new(decrypt(@otp_keys[id])).now : 0 - end + # Get last sync + def get_last_sync + return @config['last_sync'].to_i + rescue + return 0 + end - # Get remaining time before expire otp code - # @return [Integer] time in seconde - def get_otp_remaining_time - (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i - end + # Sync data with remote file + # @args: force -> force the sync + def sync(force=false) + return if @config.empty? or @config['protocol'].to_s.empty? + return if get_last_sync + 300 > Time.now.to_i and not force - # Generate a random password - # @param options [Hash] :length, :special, :alpha, :numeric - # @return [String] a random string - def self.password(**options) - length = - if !options.include?(:length) || options[:length].to_i <= 0 - 8 - elsif options[:length].to_i >= 32_768 - 32_768 - else - options[:length].to_i - end + tmp_file = "#{@wallet_file}.sync" + + case @config['protocol'] + when 'sftp', 'scp', 'ssh' + require "mpw/sync/ssh" + sync = SyncSSH.new(@config) + when 'ftp' + require 'mpw/sync/ftp' + sync = SyncFTP.new(@config) + else + raise I18n.t('error.sync.unknown_type') + end - chars = [] - chars += [*('!'..'?')] - [*('0'..'9')] if options[:special] - chars += [*('A'..'Z'), *('a'..'z')] if options[:alpha] - chars += [*('0'..'9')] if options[:numeric] - chars = [*('A'..'Z'), *('a'..'z'), *('0'..'9')] if chars.empty? + sync.connect + sync.get(tmp_file) - result = '' - length.times do - result << chars.sample - end + remote = MPW.new(@key, tmp_file, @gpg_pass, @gpg_exe) + remote.read_data - result - end + File.unlink(tmp_file) if File.exist?(tmp_file) - private + return if remote.get_last_sync == @config['last_update'] - # Decrypt a gpg file - # @param data [String] data to decrypt - # @return [String] data decrypted - def decrypt(data) - return nil if data.to_s.empty? + if not remote.to_s.empty? + @data.each do |item| + update = false - password = - if /^(1\.[0-9.]+|2\.0)(\.[0-9]+)?/ =~ GPGME::Engine.info.first.version || @pinmode - { password: @gpg_pass } - else - { password: @gpg_pass, - pinentry_mode: GPGME::PINENTRY_MODE_LOOPBACK } - end + remote.list.each do |r| + next if item.id != r.id - crypto = GPGME::Crypto.new(armor: true) - crypto - .decrypt(data, password) - .read.force_encoding('utf-8') - rescue => e - raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}" - end + # Update item + if item.last_edit < r.last_edit + item.update(group: r.group, + host: r.host, + protocol: r.protocol, + user: r.user, + port: r.port, + comment: r.comment + ) + set_password(item.id, remote.get_password(item.id)) + end - # Encrypt a file - # @param data [String] data to encrypt - # @return [String] data encrypted - def encrypt(data) - recipients = [] - crypto = GPGME::Crypto.new(armor: true, always_trust: true) + r.delete + update = true - recipients.push(@key) - @keys.each_key do |key| - next if key == @key - recipients.push(key) - end + break + end + + # Remove an old item + if not update and item.last_sync.to_i < get_last_sync and item.last_edit < get_last_sync + item.delete + end + end + end + + # Add item + remote.list.each do |r| + next if r.last_edit <= get_last_sync + + item = Item.new(id: r.id, + group: r.group, + host: r.host, + protocol: r.protocol, + user: r.user, + port: r.port, + comment: r.comment, + created: r.created, + last_edit: r.last_edit + ) + + set_password(item.id, remote.get_password(item.id)) + add(item) + end + + remote = nil + + @data.each do |item| + item.set_last_sync + end + + @config['last_sync'] = Time.now.to_i + + write_data + sync.update(@wallet_file) + rescue Exception => e + File.unlink(tmp_file) if File.exist?(tmp_file) + + raise "#{I18n.t('error.sync.general')}\n#{e}" + end + + # Set an opt key + # args: id -> the item id + # key -> the new key + def set_otp_key(id, key) + if not key.to_s.empty? + @otp_keys[id] = encrypt(key.to_s) + end + end + + # Get an opt key + # args: id -> the item id + # key -> the new key + def get_otp_key(id) + if @otp_keys.has_key?(id) + return decrypt(@otp_keys[id]) + else + return nil + end + end + + + # Get an otp code + # @args: id -> the item id + # @rtrn: an otp code + def get_otp_code(id) + if not @otp_keys.has_key?(id) + return 0 + else + return ROTP::TOTP.new(decrypt(@otp_keys[id])).now + end + end + + # Get remaining time before expire otp code + # @rtrn: return time in seconde + def get_otp_remaining_time + return (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i + end + + # Generate a random password + # @args: options -> :length, :special, :alpha, :numeric + # @rtrn: a random string + def self.password(options={}) + if not options.include?(:length) or options[:length].to_i <= 0 + length = 8 + elsif options[:length].to_i >= 32768 + length = 32768 + else + length = options[:length].to_i + end + + chars = [] + chars += [*('!'..'?')] - [*('0'..'9')] if options.include?(:special) + chars += [*('A'..'Z'),*('a'..'z')] if options.include?(:alpha) + chars += [*('0'..'9')] if options.include?(:numeric) + chars = [*('A'..'Z'),*('a'..'z'),*('0'..'9')] if chars.empty? + + result = '' + while length > 62 do + result << chars.sample(62).join + length -= 62 + end + result << chars.sample(length).join + + return result + end + + # Decrypt a gpg file + # @args: data -> string to decrypt + private + def decrypt(data) + return nil if data.to_s.empty? + + crypto = GPGME::Crypto.new(armor: true) + + return crypto.decrypt(data, password: @gpg_pass).read.force_encoding('utf-8') + rescue Exception => e + raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}" + end + + # Encrypt a file + # args: data -> string to encrypt + private + def encrypt(data) + recipients = [] + crypto = GPGME::Crypto.new(armor: true, always_trust: true) + + recipients.push(@key) + @keys.each_key do |key| + next if key == @key + recipients.push(key) + end + + return crypto.encrypt(data, recipients: recipients).read + rescue Exception => e + raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}" + end - crypto.encrypt(data, recipients: recipients).read - rescue => e - raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}" - end - end +end end diff --git a/lib/mpw/sync/ftp.rb b/lib/mpw/sync/ftp.rb new file mode 100644 index 0000000..d64e629 --- /dev/null +++ b/lib/mpw/sync/ftp.rb @@ -0,0 +1,68 @@ +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'i18n' +require 'net/ftp' + +module MPW +class FTP + + # Constructor + # @args: config -> the config + def initialize(config) + @host = config['host'] + @user = config['user'] + @password = config['password'] + @path = config['path'] + @port = config['port'].instance_of?(Integer) ? 22 : config['port'] + end + + + # Connect to server + def connect + Net::FTP.open(@host) do |ftp| + ftp.login(@user, @password) + break + end + rescue Exception => e + raise "#{I18n.t('error.sync.connection')}\n#{e}" + end + + # Get data on server + # @args: file_tmp -> the path where download the file + def get(file_tmp) + Net::FTP.open(@host) do |ftp| + ftp.login(@user, @password) + ftp.gettextfile(@path, file_tmp) + end + rescue Exception => e + raise "#{I18n.t('error.sync.download')}\n#{e}" + end + + # Update the remote data + # @args: file_gpg -> the data to send on server + def update(file_gpg) + Net::FTP.open(@host) do |ftp| + ftp.login(@user, @password) + ftp.puttextfile(file_gpg, @path) + end + rescue Exception => e + raise "#{I18n.t('error.sync.upload')}\n#{e}" + end +end +end diff --git a/lib/mpw/sync/ssh.rb b/lib/mpw/sync/ssh.rb new file mode 100644 index 0000000..4471949 --- /dev/null +++ b/lib/mpw/sync/ssh.rb @@ -0,0 +1,67 @@ +#!/usr/bin/ruby +# MPW is a software to crypt and manage your passwords +# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'i18n' +require 'net/ssh' +require 'net/sftp' + +module MPW +class SyncSSH + + # Constructor + # @args: config -> the config + def initialize(config) + @host = config['host'] + @user = config['user'] + @password = config['password'] + @path = config['path'] + @port = config['port'].instance_of?(Integer) ? 22 : config['port'] + end + + # Connect to server + def connect + Net::SSH.start(@host, @user, password: @password, port: @port) do + break + end + rescue Exception => e + raise "#{I18n.t('error.sync.connection')}\n#{e}" + end + + # Get data on server + # @args: file_tmp -> the path where download the file + def get(file_tmp) + Net::SFTP.start(@host, @user, password: @password, port: @port) do |sftp| + sftp.lstat(@path) do |response| + sftp.download!(@path, file_tmp) if response.ok? + end + end + rescue Exception => e + raise "#{I18n.t('error.sync.download')}\n#{e}" + end + + # Update the remote data + # @args: file_gpg -> the data to send on server + def update(file_gpg) + Net::SFTP.start(@host, @user, password: @password, port: @port) do |sftp| + sftp.upload!(file_gpg, @path) + end + rescue Exception => e + raise "#{I18n.t('error.sync.upload')}\n#{e}" + end +end +end diff --git a/mpw.gemspec b/mpw.gemspec index abcb375..80d9344 100644 --- a/mpw.gemspec +++ b/mpw.gemspec @@ -1,3 +1,4 @@ +# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) @@ -11,18 +12,18 @@ Gem::Specification.new do |spec| spec.homepage = 'https://github.com/nishiki/manage-password' spec.license = 'GPL-2.0' - spec.files = %x(git ls-files -z).split("\x0") + spec.files = `git ls-files -z`.split("\x0") spec.executables = ['mpw'] spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 2.1' - - spec.add_dependency 'i18n', '~> 0.9', '>= 0.9.1' - spec.add_dependency 'gpgme', '~> 2.0', '>= 2.0.14' - spec.add_dependency 'highline', '~> 1.7', '>= 1.7.8' - spec.add_dependency 'locale', '~> 2.1', '>= 2.1.2' - spec.add_dependency 'colorize', '~> 0.8', '>= 0.8.1' - spec.add_dependency 'clipboard', '~> 1.1', '>= 1.1.1' - spec.add_dependency 'rotp', '~> 3.3', '>= 3.3.0' + spec.add_dependency "i18n", "~> 0.7", ">= 0.7.0" + spec.add_dependency "gpgme", "~> 2.0", ">= 2.0.12" + spec.add_dependency "highline", "~> 1.7", ">= 1.7.8" + spec.add_dependency "locale", "~> 2.1", ">= 2.1.2" + spec.add_dependency "colorize", "~> 0.8", ">= 0.8.1" + spec.add_dependency "net-ssh", "~> 3.2", ">= 3.2.0" + spec.add_dependency "net-sftp", "~> 2.1", ">= 2.1.2" + spec.add_dependency "clipboard", "~> 1.1", ">= 1.1.1" + spec.add_dependency "rotp", "~> 3.1", ">= 3.1.0" end diff --git a/templates/add_form.erb b/templates/add_form.erb index e2aea20..c2d6d73 100644 --- a/templates/add_form.erb +++ b/templates/add_form.erb @@ -1,13 +1,9 @@ --- -# <%= I18n.t('form.add_item.url') %> -url: <%= options[:url] %> -# <%= I18n.t('form.add_item.login') %> -user: <%= options[:user] %> -# <%= I18n.t('form.add_item.group') %> -group: <%= options[:group] %><% unless password %> -# <%= I18n.t('form.add_item.password') %> -password:<% end %> -# <%= I18n.t('form.add_item.comment') %> -comment: <%= options[:comment] %> -# <%= I18n.t('form.add_item.otp_key') %> -otp_key: <%= options[:otp] %> +host: # <%= I18n.t('form.add_item.host') %> +user: # <%= I18n.t('form.add_item.login') %> +group: # <%= I18n.t('form.add_item.group') %> +protocol: # <%= I18n.t('form.add_item.protocol') %><% if not password %> +password: # <%= I18n.t('form.add_item.password') %><% end %> +port: # <%= I18n.t('form.add_item.port') %> +comment: # <%= I18n.t('form.add_item.comment') %> +otp_key: # <%= I18n.t('form.add_item.otp_key') %> diff --git a/templates/setup_form.erb b/templates/setup_form.erb new file mode 100644 index 0000000..f7e6d7b --- /dev/null +++ b/templates/setup_form.erb @@ -0,0 +1,9 @@ +--- +# <%= I18n.t('form.setup_config.lang') %> +language: <%= @config.lang %> +# <%= I18n.t('form.setup_config.gpg_key') %> +gpg_key: <%= @config.key %> +# <%= I18n.t('form.setup_config.wallet_dir') %> +wallet_dir: <%= @config.config_dir %> +# <%= I18n.t('form.setup_config.gpg_exe') %> +gpg_exe: <%= @config.gpg_exe %> diff --git a/templates/update_form.erb b/templates/update_form.erb index eafeb1b..f4a380b 100644 --- a/templates/update_form.erb +++ b/templates/update_form.erb @@ -1,13 +1,17 @@ --- -# <%= I18n.t('form.update_item.url') %> -host: <% if options[:url] %><%= options[:url] %><% else %><%= item.url %><% end %> +# <%= I18n.t('form.update_item.host') %> +host: <%= item.host %> # <%= I18n.t('form.update_item.login') %> -user: <% if options[:user] %><%= options[:user] %><% else %><%= item.user %><% end %><% unless password %> +user: <%= item.user %> # <%= I18n.t('form.update_item.password') %> -password: <% end %> +password: # <%= I18n.t('form.update_item.group') %> -group: <% if options[:group] %><%= options[:group] %><% else %><%= item.group %><% end %> +group: <%= item.group %> +# <%= I18n.t('form.update_item.protocol') %> +protocol: <%= item.protocol %> +# <%= I18n.t('form.update_item.port') %> +port: <%= item.port %> # <%= I18n.t('form.update_item.otp_key') %> -otp_key: <% if options[:otp_key] %><%= options[:otp_key] %><% end %> +opt_key: # <%= I18n.t('form.update_item.comment') %> -comment: <% if options[:comment] %><%= options[:comment] %><% else %><%= item.comment %><% end %> +comment: <%= item.comment %> diff --git a/test/files/fixtures-import.yml b/test/files/fixtures-import.yml deleted file mode 100644 index 8cb1ff4..0000000 --- a/test/files/fixtures-import.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -1: - url: https://fric.com - user: 230403 - group: Bank - password: 5XdiTQOubRDw9B0aJoMlcEyL - otp_key: 330223432 - comment: I love my bank -2: - url: https://assurance.com:443 - user: user_2132 - host: assurance.com - group: Assurance - password: DMyK6B3v4bWO52VzU7aTHIem - otp_key: - comment: diff --git a/test/files/fixtures.yml b/test/files/fixtures.yml index d8927cb..5651911 100644 --- a/test/files/fixtures.yml +++ b/test/files/fixtures.yml @@ -1,31 +1,28 @@ -add: - url: 'https://example.com:8080' - group: 'Bank' - host: 'example.com' - protocol: 'https' - user: 'admin' - password: 'VmfnCN6pPIqgRIbc' - port: '8080' - comment: 'the website' +add_new: + group: 'test_group' + host: 'test_host' + protocol: 'test_protocol' + user: 'test_user' + password: 'test_password' + port: '42' + comment: 'test_comment' -import: +add_existing: id: 'TEST-ID-XXXXX' - url: 'https://gogole.com:8081/toto' - group: 'Cloud' - host: 'gogole.com' - protocol: 'https' - user: 'gg-2304' - password: 'TITl0kV9CDDa9sVK' - port: '8081' - comment: 'My little servers' + group: 'test_group_existing' + host: 'test_host_existing' + protocol: 'test_protocol_existing' + user: 'test_user_existing' + password: 'test_password_existing' + port: '44' + comment: 'test_comment_existing' created: 1386752948 update: - url: 'ssh://example2.com:2222' - group: 'Assurance' - host: 'example2.com' - protocol: 'ssh' - user: 'root' - password: 'kbSrbv4WlMaVxaZ7' - port: '2222' - comment: 'i love ssh' + group: 'test_group_update' + host: 'test_host_update' + protocol: 'test_protocol_update' + user: 'test_user_update' + password: 'test_password_update' + port: '43' + comment: 'test_comment_update' diff --git a/test/files/import-gorilla.txt b/test/files/import-gorilla.txt deleted file mode 100644 index a5fc604..0000000 --- a/test/files/import-gorilla.txt +++ /dev/null @@ -1,4 +0,0 @@ -uuid,group,title,url,user,password,notes -49627979-e393-48c4-49ca-1cf66603238e,Bank,Fric,http://fric.com,12345,secret,money money -49627979-e393-48c4-49ca-1cf66603238f,,My little server,server.com,secret2, -49627979-e393-48c4-49ca-1cf66603238g,Cloud,,ssh://fric.com:4333,username,secret,bastion diff --git a/test/files/import-keepass.txt b/test/files/import-keepass.txt deleted file mode 100644 index 61ce7ad..0000000 --- a/test/files/import-keepass.txt +++ /dev/null @@ -1,3 +0,0 @@ -"Group","Title","Username","Password","URL","Notes" -"Racine","Bank","123456","ywcExJW8qmBVTSyi","http://bank.com/login","My little bank" -"Racine/Cloud","GAFAM","wesh","superpassword","localhost.local","" diff --git a/test/files/import-mpw_old.txt b/test/files/import-mpw_old.txt deleted file mode 100644 index fd162aa..0000000 --- a/test/files/import-mpw_old.txt +++ /dev/null @@ -1,35 +0,0 @@ ---- -1: - host: fric.com - user: 12345 - group: Bank - password: secret - protocol: http - port: - otp_key: - comment: Fric money money - last_edit: 1487623641 - created: 1485729356 -2: - host: server.com - user: sercret2 - group: - password: - protocol: - port: 4222 - otp_key: - comment: My little server - last_edit: 1487623641 - created: 1485729356 -3: - host: fric.com - user: username - group: Cloud - password: - protocol: ssh - port: 4333 - otp_key: - comment: bastion - last_edit: 1487623641 - created: 1485729356 - diff --git a/test/init.rb b/test/init.rb deleted file mode 100644 index 08ebbb8..0000000 --- a/test/init.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'fileutils' -require 'gpgme' - -FileUtils.rm_rf("#{Dir.home}/.config/mpw") -FileUtils.rm_rf("#{Dir.home}/.gnupg") - -param = '' -param << '<GnupgKeyParms format="internal">' + "\n" -param << "Key-Type: RSA\n" -param << "Key-Length: 512\n" -param << "Subkey-Type: ELG-E\n" -param << "Subkey-Length: 512\n" -param << "Name-Real: test\n" -param << "Name-Comment: test\n" -param << "Name-Email: test2@example.com\n" -param << "Expire-Date: 0\n" -param << "Passphrase: password\n" -param << "</GnupgKeyParms>\n" - -ctx = GPGME::Ctx.new -ctx.genkey(param, nil, nil) diff --git a/test/test.sh b/test/test.sh new file mode 100644 index 0000000..ab37885 --- /dev/null +++ b/test/test.sh @@ -0,0 +1 @@ +echo "test\ntest\n" | ruby ./bin/mpw config --init test@test.com diff --git a/test/test2.rb b/test/test2.rb new file mode 100644 index 0000000..17af2da --- /dev/null +++ b/test/test2.rb @@ -0,0 +1,11 @@ +require 'open3' + +Open3.popen3("./bin/mpw config --init test@test.com") do |stdin, stdout, stderr, thread| + stdin.puts 'test' + stdin.puts 'test' +end + +Open3.popen3("./bin/mpw list") do |stdin, stdout, stderr, thread| + stdin.puts 'test' + puts stdout +end diff --git a/test/test_cli.rb b/test/test_cli.rb deleted file mode 100644 index 8bae4cc..0000000 --- a/test/test_cli.rb +++ /dev/null @@ -1,256 +0,0 @@ -require 'i18n' -require 'test/unit' - -class TestConfig < Test::Unit::TestCase - def setup - if defined?(I18n.enforce_available_locales) - I18n.enforce_available_locales = true - end - - I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) - I18n.load_path = ["#{File.expand_path('../../i18n', __FILE__)}/en.yml"] - I18n.locale = :en - - @password = 'password' - @fixtures = YAML.load_file('./test/files/fixtures.yml') - @gpg_key = 'test@example.com' - end - - def test_00_init_config - output = %x( - echo "#{@password}\n#{@password}" | mpw config \ - --init #{@gpg_key} \ - 2>/dev/null - ) - assert_match(I18n.t('form.setup_config.valid'), output) - assert_match(I18n.t('form.setup_gpg_key.valid'), output) - end - - def test_01_add_item - data = @fixtures['add'] - - output = %x( - echo #{@password} | mpw add \ - --url #{data['url']} \ - --user #{data['user']} \ - --comment '#{data['comment']}' \ - --group #{data['group']} \ - --random \ - 2>/dev/null - ) - assert_match(I18n.t('form.add_item.valid'), output) - - output = %x(echo #{@password} | mpw list 2>/dev/null) - assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output) - assert_match(data['user'], output) - assert_match(data['comment'], output) - assert_match(data['group'], output) - end - - def test_02_search - data = @fixtures['add'] - - output = %x(echo #{@password} | mpw list --group #{data['group']} 2>/dev/null) - assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output) - - output = %x(echo #{@password} | mpw list --pattern #{data['host']} 2>/dev/null) - assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output) - - output = %x(echo #{@password} | mpw list --pattern #{data['comment']} 2>/dev/null) - assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output) - - output = %x(echo #{@password} | mpw list --group R1Pmfbp626TFpjlr 2>/dev/null) - assert_match(I18n.t('display.nothing'), output) - - output = %x(echo #{@password} | mpw list --pattern h1IfnKqamaGM9oEX 2>/dev/null) - assert_match(I18n.t('display.nothing'), output) - end - - def test_03_update_item - data = @fixtures['update'] - - output = %x( - echo #{@password} | mpw update \ - -p #{@fixtures['add']['host']} \ - --url #{data['url']} \ - --user #{data['user']} \ - --comment '#{data['comment']}' \ - --new-group #{data['group']} \ - 2>/dev/null - ) - assert_match(I18n.t('form.update_item.valid'), output) - - output = %x(echo #{@password} | mpw list 2>/dev/null) - assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output) - assert_match(data['user'], output) - assert_match(data['comment'], output) - assert_match(data['group'], output) - end - - def test_04_delete_item - output = %x( - echo "#{@password}\ny" | mpw delete \ - -p #{@fixtures['update']['host']} \ - 2>/dev/null - ) - assert_match(I18n.t('form.delete_item.valid'), output) - - output = %x(echo #{@password} | mpw list 2>/dev/null) - assert_match(I18n.t('display.nothing'), output) - end - - def test_05_import_export - file_import = './test/files/fixtures-import.yml' - file_export = '/tmp/test-mpw.yml' - - output = %x(echo #{@password} | mpw import --file #{file_import} 2>/dev/null) - assert_match(I18n.t('form.import.valid', file: file_import), output) - - output = %x(echo #{@password} | mpw export --file #{file_export} 2>/dev/null) - assert_match(I18n.t('form.export.valid', file: file_export), output) - assert(File.exist?(file_export)) - assert_equal(YAML.load_file(file_export).length, 2) - - YAML.load_file(file_import).each_value do |import| - error = true - - YAML.load_file(file_export).each_value do |export| - next if import['url'] != export['url'] - - %w[user group password protocol port otp_key comment].each do |key| - assert_equal(import[key].to_s, export[key].to_s) - end - - error = false - break - end - - assert(!error) - end - end - - def test_06_copy - data = YAML.load_file('./test/files/fixtures-import.yml')[2] - - output = %x( - echo "#{@password}\np\nq" | mpw copy \ - --disable-clipboard \ - -p #{data['host']} \ - 2>/dev/null - ) - assert_match(data['password'], output) - end - - def test_07_setup_wallet - gpg_key = 'test2@example.com' - - output = %x(echo #{@password} | mpw wallet --add-gpg-key #{gpg_key} 2>/dev/null) - assert_match(I18n.t('form.add_key.valid'), output) - - output = %x(echo #{@password} | mpw wallet --list-keys 2>/dev/null) - assert_match("| #{@gpg_key}", output) - assert_match("| #{gpg_key}", output) - - output = %x(echo #{@password} | mpw wallet --delete-gpg-key #{gpg_key} 2>/dev/null) - assert_match(I18n.t('form.delete_key.valid'), output) - - output = %x(echo #{@password} | mpw wallet --list-keys 2>/dev/null) - assert_match("| #{@gpg_key}", output) - assert_no_match(/\| #{gpg_key}/, output) - - output = %x(mpw wallet) - assert_match('| default', output) - - output = %x(mpw wallet --path '.') - assert_match(I18n.t('form.set_wallet_path.valid'), output) - - output = %x(mpw config) - assert_match(%r{path_wallet_default.+\| #{Dir.pwd}/default.mpw}, output) - assert(File.exist?("#{Dir.pwd}/default.mpw")) - - output = %x(mpw wallet) - assert_match('default', output) - - output = %x(mpw wallet --default-path) - assert_match(I18n.t('form.set_wallet_path.valid'), output) - - output = %x(mpw config) - assert_no_match(/path_wallet_default/, output) - end - - def test_08_setup_config - gpg_key = 'test2@example.com' - gpg_exe = '/usr/bin/gpg2' - wallet_dir = '/tmp' - length = 24 - wallet = 'work' - - output = %x( - mpw config \ - --gpg-exe #{gpg_exe} \ - --key #{gpg_key} \ - --enable-pinmode \ - --disable-alpha \ - --disable-special-chars \ - --disable-numeric \ - --length #{length} \ - --wallet-dir #{wallet_dir} \ - --default-wallet #{wallet} - ) - assert_match(I18n.t('form.set_config.valid'), output) - - output = %x(mpw config) - assert_match(/gpg_key.+\| #{gpg_key}/, output) - assert_match(/gpg_exe.+\| #{gpg_exe}/, output) - assert_match(/pinmode.+\| true/, output) - assert_match(/default_wallet.+\| #{wallet}/, output) - assert_match(/wallet_dir.+\| #{wallet_dir}/, output) - assert_match(/password_length.+\| #{length}/, output) - %w[numeric alpha special].each do |k| - assert_match(/password_#{k}.+\| false/, output) - end - - output = %x( - mpw config \ - --gpg-exe '' \ - --key #{@gpg_key} \ - --alpha \ - --special-chars \ - --numeric \ - --disable-pinmode - ) - assert_match(I18n.t('form.set_config.valid'), output) - - output = %x(mpw config) - assert_match(/gpg_key.+\| #{@gpg_key}/, output) - assert_match(/pinmode.+\| false/, output) - %w[numeric alpha special].each do |k| - assert_match(/password_#{k}.+\| true/, output) - end - end - - def test_09_generate_password - length = 24 - - output = %x( - mpw genpwd \ - --length #{length} \ - --alpha - ) - assert_match(/[a-zA-Z]{#{length}}/, output) - - output = %x( - mpw genpwd \ - --length #{length} \ - --numeric - ) - assert_match(/[0-9]{#{length}}/, output) - - output = %x( - mpw genpwd \ - --length #{length} \ - --special-chars - ) - assert_no_match(/[a-zA-Z0-9]/, output) - end -end diff --git a/test/test_config.rb b/test/test_config.rb index 2b88b54..3de438c 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -1,79 +1,40 @@ +#!/usr/bin/ruby + require 'mpw/config' require 'test/unit' require 'locale' require 'i18n' class TestConfig < Test::Unit::TestCase - def setup - lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] + def setup + lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] + + if defined?(I18n.enforce_available_locales) + I18n.enforce_available_locales = true + end + + I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) + I18n.load_path = Dir["#{File.expand_path('../../i18n', __FILE__)}/*.yml"] + I18n.default_locale = :en + I18n.locale = lang.to_sym + end - if defined?(I18n.enforce_available_locales) - I18n.enforce_available_locales = true - end + def test_00_config + data = { key: 'test@example.com', + lang: 'en', + wallet_dir: '/tmp/test', + gpg_exe: '', + } - I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) - I18n.load_path = Dir["#{File.expand_path('../../i18n', __FILE__)}/*.yml"] - I18n.default_locale = :en - I18n.locale = lang.to_sym - end + @config = MPW::Config.new + @config.setup(data[:key], data[:lang], data[:wallet_dir], data[:gpg_exe]) + @config.load_config - def test_00_config - data = { - gpg_key: 'test@example.com', - lang: 'en', - wallet_dir: '/tmp/test', - gpg_exe: '' - } + data.each do |k,v| + assert_equal(v, @config.send(k)) + end - @config = MPW::Config.new - @config.setup(data) - @config.load_config - - data.each do |k, v| - assert_equal(v, @config.send(k)) - end - - @config.setup_gpg_key('password', 'test@example.com', 2048) - assert(@config.check_gpg_key?) - end - - def test_01_password - data = { - pwd_alpha: false, - pwd_numeric: false, - pwd_special: true, - pwd_length: 32 - } - - @config = MPW::Config.new - @config.load_config - - assert_equal(@config.password[:length], 16) - assert(@config.password[:alpha]) - assert(@config.password[:numeric]) - assert(!@config.password[:special]) - - @config.setup(data) - @config.load_config - - assert_equal(@config.password[:length], data[:pwd_length]) - assert(!@config.password[:alpha]) - assert(!@config.password[:numeric]) - assert(@config.password[:special]) - end - - def test_02_wallet_paths - new_path = '/tmp/mpw-test' - - @config = MPW::Config.new - @config.load_config - - assert(!@config.wallet_paths['default']) - - @config.set_wallet_path(new_path, 'default') - assert_equal(@config.wallet_paths['default'], new_path) - - @config.set_wallet_path('default', 'default') - assert(!@config.wallet_paths['default']) - end + @config.setup_gpg_key('password', 'test@example.com', 2048) + assert(@config.check_gpg_key?) + end end diff --git a/test/test_import.rb b/test/test_import.rb deleted file mode 100644 index f1a8727..0000000 --- a/test/test_import.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'i18n' -require 'test/unit' - -class TestImport < Test::Unit::TestCase - def setup - if defined?(I18n.enforce_available_locales) - I18n.enforce_available_locales = true - end - - I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) - I18n.load_path = ["#{File.expand_path('../../i18n', __FILE__)}/en.yml"] - I18n.locale = :en - - @password = 'password' - end - - def test_00_import_mpw_old - file = './test/files/import-mpw_old.txt' - format = 'mpw_old' - - output = %x( - mpw import \ - --file #{file} \ - --format #{format} \ - --wallet #{format} - ) - assert_match(I18n.t('form.import.valid'), output) - - output = %x(echo #{@password} | mpw list --group Bank --wallet #{format}) - assert_match(%r{http://.*fric\.com.*12345.*Fric money money}, output) - - output = %x(echo #{@password} | mpw list --group Cloud --wallet #{format}) - assert_match(%r{ssh://.*fric\.com.*:4333.*username.*bastion}, output) - - output = %x(echo #{@password} | mpw list --wallet #{format}) - assert_match(/server\.com.*My little server/, output) - end - - def test_01_import_gorilla - file = './test/files/import-gorilla.txt' - format = 'gorilla' - - output = %x( - mpw import \ - --file #{file} \ - --format #{format} \ - --wallet #{format} - ) - assert_match(I18n.t('form.import.valid'), output) - - output = %x(echo #{@password} | mpw list --group Bank --wallet #{format}) - assert_match(%r{http://.*fric\.com.*12345.*Fric money money}, output) - - output = %x(echo #{@password} | mpw list --group Cloud --wallet #{format}) - assert_match(%r{ssh://.*fric\.com.*:4333.*username.*bastion}, output) - - output = %x(echo #{@password} | mpw list --wallet #{format}) - assert_match(/server\.com.*My little server/, output) - end - - def test_02_import_keepass - file = './test/files/import-keepass.txt' - format = 'keepass' - - output = %x( - mpw import \ - --file #{file} \ - --format #{format} \ - --wallet #{format} - ) - assert_match(I18n.t('form.import.valid'), output) - - output = %x(echo #{@password} | mpw list --group 'Racine/Cloud' --wallet #{format}) - assert_match(/localhost\.local.*wesh.*GAFAM/, output) - - output = %x(echo #{@password} | mpw list --wallet #{format}) - assert_match(%r{http://.*bank\.com.*123456.*Bank My little bank}, output) - end -end diff --git a/test/test_item.rb b/test/test_item.rb index 97503c0..b9f5961 100644 --- a/test/test_item.rb +++ b/test/test_item.rb @@ -1,165 +1,172 @@ +#!/usr/bin/ruby + require 'mpw/item' require 'test/unit' require 'yaml' - + class TestItem < Test::Unit::TestCase - def setup - if defined?(I18n.enforce_available_locales) - I18n.enforce_available_locales = false - end + def setup + @fixture_file = 'test/files/fixtures.yml' + @fixtures = YAML.load_file(@fixture_file) + + if defined?(I18n.enforce_available_locales) + I18n.enforce_available_locales = false + end - I18n.load_path = Dir['./i18n/cli/*.yml'] - I18n.default_locale = :en + I18n.load_path = Dir['./i18n/cli/*.yml'] + I18n.default_locale = :en - @fixtures = YAML.load_file('./test/files/fixtures.yml') - end - def test_00_add_without_name - assert_raise(RuntimeError) { MPW::Item.new } - end + puts + end - def test_01_add - data = { - group: @fixtures['add']['group'], - user: @fixtures['add']['user'], - url: @fixtures['add']['url'], - comment: @fixtures['add']['comment'] - } + def test_00_add_without_name + assert_raise(RuntimeError){MPW::Item.new} + end - item = MPW::Item.new(data) + def test_01_add_new + data = { group: @fixtures['add_new']['group'], + host: @fixtures['add_new']['host'], + protocol: @fixtures['add_new']['protocol'], + user: @fixtures['add_new']['user'], + port: @fixtures['add_new']['port'], + comment: @fixtures['add_new']['comment'], + } + + item = MPW::Item.new(data) - assert(!item.nil?) - assert(!item.empty?) + assert(!item.nil?) + assert(!item.empty?) - assert_equal(@fixtures['add']['url'], item.url) - assert_equal(@fixtures['add']['group'], item.group) - assert_equal(@fixtures['add']['host'], item.host) - assert_equal(@fixtures['add']['protocol'], item.protocol) - assert_equal(@fixtures['add']['user'], item.user) - assert_equal(@fixtures['add']['port'].to_i, item.port) - assert_equal(@fixtures['add']['comment'], item.comment) - end + assert_equal(@fixtures['add_new']['group'], item.group) + assert_equal(@fixtures['add_new']['host'], item.host) + assert_equal(@fixtures['add_new']['protocol'], item.protocol) + assert_equal(@fixtures['add_new']['user'], item.user) + assert_equal(@fixtures['add_new']['port'].to_i, item.port) + assert_equal(@fixtures['add_new']['comment'], item.comment) + end - def test_02_import - data = { - id: @fixtures['import']['id'], - group: @fixtures['import']['group'], - user: @fixtures['import']['user'], - url: @fixtures['import']['url'], - comment: @fixtures['import']['comment'], - created: @fixtures['import']['created'] - } + def test_02_add_existing + data = { id: @fixtures['add_existing']['id'], + group: @fixtures['add_existing']['group'], + host: @fixtures['add_existing']['host'], + protocol: @fixtures['add_existing']['protocol'], + user: @fixtures['add_existing']['user'], + port: @fixtures['add_existing']['port'], + comment: @fixtures['add_existing']['comment'], + created: @fixtures['add_existing']['created'], + } - item = MPW::Item.new(data) + item = MPW::Item.new(data) - assert(!item.nil?) - assert(!item.empty?) + assert(!item.nil?) + assert(!item.empty?) - assert_equal(@fixtures['import']['id'], item.id) - assert_equal(@fixtures['import']['url'], item.url) - assert_equal(@fixtures['import']['group'], item.group) - assert_equal(@fixtures['import']['host'], item.host) - assert_equal(@fixtures['import']['protocol'], item.protocol) - assert_equal(@fixtures['import']['user'], item.user) - assert_equal(@fixtures['import']['port'].to_i, item.port) - assert_equal(@fixtures['import']['comment'], item.comment) - assert_equal(@fixtures['import']['created'], item.created) - end + assert_equal(@fixtures['add_existing']['id'], item.id) + assert_equal(@fixtures['add_existing']['group'], item.group) + assert_equal(@fixtures['add_existing']['host'], item.host) + assert_equal(@fixtures['add_existing']['protocol'], item.protocol) + assert_equal(@fixtures['add_existing']['user'], item.user) + assert_equal(@fixtures['add_existing']['port'].to_i, item.port) + assert_equal(@fixtures['add_existing']['comment'], item.comment) + assert_equal(@fixtures['add_existing']['created'], item.created) + end - def test_03_update - data = { - group: @fixtures['add']['group'], - user: @fixtures['add']['user'], - url: @fixtures['add']['url'], - comment: @fixtures['add']['comment'] - } + def test_03_update + data = { group: @fixtures['add_new']['group'], + host: @fixtures['add_new']['host'], + protocol: @fixtures['add_new']['protocol'], + user: @fixtures['add_new']['user'], + port: @fixtures['add_new']['port'], + comment: @fixtures['add_new']['comment'], + } + + item = MPW::Item.new(data) - item = MPW::Item.new(data) + assert(!item.nil?) + assert(!item.empty?) - assert(!item.nil?) - assert(!item.empty?) + created = item.created + last_edit = item.last_edit - created = item.created - last_edit = item.last_edit + data = { group: @fixtures['update']['group'], + host: @fixtures['update']['host'], + protocol: @fixtures['update']['protocol'], + user: @fixtures['update']['user'], + port: @fixtures['update']['port'], + comment: @fixtures['update']['comment'], + } + + sleep(1) + assert(item.update(data)) - data = { - group: @fixtures['update']['group'], - user: @fixtures['update']['user'], - url: @fixtures['update']['url'], - comment: @fixtures['update']['comment'] - } + assert(!item.empty?) - sleep(1) - assert(item.update(data)) + assert_equal(@fixtures['update']['group'], item.group) + assert_equal(@fixtures['update']['host'], item.host) + assert_equal(@fixtures['update']['protocol'], item.protocol) + assert_equal(@fixtures['update']['user'], item.user) + assert_equal(@fixtures['update']['port'].to_i, item.port) + assert_equal(@fixtures['update']['comment'], item.comment) - assert(!item.empty?) + assert_equal(created, item.created) + assert_not_equal(last_edit, item.last_edit) + end - assert_equal(@fixtures['update']['url'], item.url) - assert_equal(@fixtures['update']['group'], item.group) - assert_equal(@fixtures['update']['host'], item.host) - assert_equal(@fixtures['update']['protocol'], item.protocol) - assert_equal(@fixtures['update']['user'], item.user) - assert_equal(@fixtures['update']['port'].to_i, item.port) - assert_equal(@fixtures['update']['comment'], item.comment) + def test_05_update_one_element + data = { group: @fixtures['add_new']['group'], + host: @fixtures['add_new']['host'], + protocol: @fixtures['add_new']['protocol'], + user: @fixtures['add_new']['user'], + port: @fixtures['add_new']['port'], + comment: @fixtures['add_new']['comment'], + } + + item = MPW::Item.new(data) - assert_equal(created, item.created) - assert_not_equal(last_edit, item.last_edit) - end + assert(!item.nil?) + assert(!item.empty?) - def test_05_update_one_element - data = { - group: @fixtures['add']['group'], - user: @fixtures['add']['user'], - url: @fixtures['add']['url'], - comment: @fixtures['add']['comment'] - } + last_edit = item.last_edit - item = MPW::Item.new(data) + sleep(1) + assert(item.update({comment: @fixtures['update']['comment']})) - assert(!item.nil?) - assert(!item.empty?) + assert_equal(@fixtures['add_new']['group'], item.group) + assert_equal(@fixtures['add_new']['host'], item.host) + assert_equal(@fixtures['add_new']['protocol'], item.protocol) + assert_equal(@fixtures['add_new']['user'], item.user) + assert_equal(@fixtures['add_new']['port'].to_i, item.port) + assert_equal(@fixtures['update']['comment'], item.comment) + + assert_not_equal(last_edit, item.last_edit) + end - last_edit = item.last_edit + def test_05_delete + data = { group: @fixtures['add_new']['group'], + host: @fixtures['add_new']['host'], + protocol: @fixtures['add_new']['protocol'], + user: @fixtures['add_new']['user'], + port: @fixtures['add_new']['port'], + comment: @fixtures['add_new']['comment'], + } + + item = MPW::Item.new(data) - sleep(1) - item.update(comment: @fixtures['update']['comment']) + assert(!item.nil?) + assert(!item.empty?) - assert_equal(@fixtures['add']['url'], item.url) - assert_equal(@fixtures['add']['group'], item.group) - assert_equal(@fixtures['add']['host'], item.host) - assert_equal(@fixtures['add']['protocol'], item.protocol) - assert_equal(@fixtures['add']['user'], item.user) - assert_equal(@fixtures['add']['port'].to_i, item.port) - assert_equal(@fixtures['update']['comment'], item.comment) + item.delete + assert(!item.nil?) + assert(item.empty?) - assert_not_equal(last_edit, item.last_edit) - end - - def test_05_delete - data = { - group: @fixtures['add']['group'], - user: @fixtures['add']['user'], - url: @fixtures['add']['url'], - comment: @fixtures['add']['comment'] - } - - item = MPW::Item.new(data) - - assert(!item.nil?) - assert(!item.empty?) - - item.delete - assert(!item.nil?) - assert(item.empty?) - - assert_equal(nil, item.id) - assert_equal(nil, item.url) - assert_equal(nil, item.group) - assert_equal(nil, item.host) - assert_equal(nil, item.protocol) - assert_equal(nil, item.user) - assert_equal(nil, item.port) - assert_equal(nil, item.comment) - assert_equal(nil, item.created) - end -end + assert_equal(nil, item.id) + assert_equal(nil, item.group) + assert_equal(nil, item.host) + assert_equal(nil, item.protocol) + assert_equal(nil, item.user) + assert_equal(nil, item.port) + assert_equal(nil, item.comment) + assert_equal(nil, item.created) + end +end diff --git a/test/test_mpw.rb b/test/test_mpw.rb index 8fc544e..0c6a25b 100644 --- a/test/test_mpw.rb +++ b/test/test_mpw.rb @@ -1,130 +1,121 @@ +#!/usr/bin/ruby + require 'mpw/mpw' require 'mpw/item' require 'test/unit' require 'yaml' require 'csv' - + class TestMPW < Test::Unit::TestCase - def setup - wallet_file = 'default.gpg' - key = 'test@example.com' - password = 'password' + def setup + fixture_file = './test/files/fixtures.yml' - if defined?(I18n.enforce_available_locales) - I18n.enforce_available_locales = false - end + wallet_file = 'default.gpg' + key = 'test@example.com' + password = 'password' - @mpw = MPW::MPW.new(key, wallet_file, password) - @fixtures = YAML.load_file('./test/files/fixtures.yml') - end + if defined?(I18n.enforce_available_locales) + I18n.enforce_available_locales = false + end - def test_00_decrypt_empty_file - @mpw.read_data - assert_equal(0, @mpw.list.length) - end + @mpw = MPW::MPW.new(key, wallet_file, password) + @fixtures = YAML.load_file(fixture_file) + end + + def test_00_decrypt_empty_file + @mpw.read_data + assert_equal(0, @mpw.list.length) + end - def test_01_encrypt_empty_file - @mpw.read_data - @mpw.write_data - end + def test_01_encrypt_empty_file + @mpw.read_data + @mpw.write_data + end - def test_02_add_item - data = { - group: @fixtures['add']['group'], - user: @fixtures['add']['user'], - url: @fixtures['add']['url'], - comment: @fixtures['add']['comment'] - } + def test_02_add_item + data = { group: @fixtures['add_new']['group'], + host: @fixtures['add_new']['host'], + protocol: @fixtures['add_new']['protocol'], + user: @fixtures['add_new']['user'], + port: @fixtures['add_new']['port'], + comment: @fixtures['add_new']['comment'], + } + + item = MPW::Item.new(data) - item = MPW::Item.new(data) + assert(!item.nil?) + assert(!item.empty?) - assert(!item.nil?) - assert(!item.empty?) + @mpw.read_data + @mpw.add(item) + @mpw.set_password(item.id, @fixtures['add_new']['password']) - @mpw.read_data - @mpw.add(item) - @mpw.set_password(item.id, @fixtures['add']['password']) + assert_equal(1, @mpw.list.length) - assert_equal(1, @mpw.list.length) + item = @mpw.list[0] + @fixtures['add_new'].each do |k,v| + if k == 'password' + assert_equal(v, @mpw.get_password(item.id)) + else + assert_equal(v, item.send(k).to_s) + end + end - item = @mpw.list[0] - @fixtures['add'].each do |k, v| - if k == 'password' - assert_equal(v, @mpw.get_password(item.id)) - else - assert_equal(v, item.send(k).to_s) - end - end + @mpw.write_data + end - @mpw.write_data - end + def test_03_decrypt_file + @mpw.read_data + assert_equal(1, @mpw.list.length) - def test_03_decrypt_file - @mpw.read_data - assert_equal(1, @mpw.list.length) + item = @mpw.list[0] + @fixtures['add_new'].each do |k,v| + if k == 'password' + assert_equal(v, @mpw.get_password(item.id)) + else + assert_equal(v, item.send(k).to_s) + end + end + end - item = @mpw.list[0] - @fixtures['add'].each do |k, v| - if k == 'password' - assert_equal(v, @mpw.get_password(item.id)) - else - assert_equal(v, item.send(k).to_s) - end - end - end + def test_04_delete_item + @mpw.read_data - def test_04_delete_item - @mpw.read_data - assert_equal(1, @mpw.list.length) + assert_equal(1, @mpw.list.length) - @mpw.list.each(&:delete) - assert_equal(0, @mpw.list.length) + @mpw.list.each do |item| + item.delete + end - @mpw.write_data - end + assert_equal(0, @mpw.list.length) - def test_05_search - @mpw.read_data + @mpw.write_data + end - @fixtures.each_value do |v| - data = { - group: v['group'], - user: v['user'], - url: v['url'], - comment: v['comment'] - } + def test_05_search + @mpw.read_data - item = MPW::Item.new(data) + @fixtures.each_value do |v| + data = { group: v['group'], + host: v['host'], + protocol: v['protocol'], + user: v['user'], + port: v['port'], + comment: v['comment'], + } + + item = MPW::Item.new(data) + + assert(!item.nil?) + assert(!item.empty?) + + @mpw.add(item) + @mpw.set_password(item.id, v['password']) + end - assert(!item.nil?) - assert(!item.empty?) - - @mpw.add(item) - @mpw.set_password(item.id, v['password']) - end - - assert_equal(3, @mpw.list.length) - assert_equal(1, @mpw.list(group: @fixtures['add']['group']).length) - assert_equal(1, @mpw.list(pattern: 'gogole').length) - assert_equal(2, @mpw.list(pattern: 'example[2\.]').length) - end - - def test_06_add_gpg_key - @mpw.read_data - - @mpw.add_key('test2@example.com') - assert_equal(2, @mpw.list_keys.length) - - @mpw.write_data - end - - def test_07_delete_gpg_key - @mpw.read_data - assert_equal(2, @mpw.list_keys.length) - - @mpw.delete_key('test2@example.com') - assert_equal(1, @mpw.list_keys.length) - - @mpw.write_data - end + assert_equal(3, @mpw.list.length) + assert_equal(1, @mpw.list(group: @fixtures['add_new']['group']).length) + assert_equal(1, @mpw.list(pattern: 'existing').length) + assert_equal(2, @mpw.list(pattern: 'host_[eu]').length) + end end diff --git a/test/test_translate.rb b/test/test_translate.rb deleted file mode 100644 index d7cbceb..0000000 --- a/test/test_translate.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'yaml' -require 'test/unit' - -class TestTranslate < Test::Unit::TestCase - def test_00_check_translate - missing = 0 - - Dir.glob('i18n/*.yml').each do |yaml| - lang = File.basename(yaml, '.yml') - translate = YAML.load_file(yaml) - - %x(grep -r -o "I18n.t('.*)" bin/ lib/ | cut -d"'" -f2).each_line do |line| - begin - t = translate[lang] - line.strip.split('.').each do |v| - t = t[v] - end - - assert(!t.to_s.empty?) - rescue - puts "#{lang}.#{line}" - missing = 1 - end - end - end - - assert_equal(0, missing) - end -end diff --git a/test/tests.rb b/test/tests.rb new file mode 100644 index 0000000..b85efc0 --- /dev/null +++ b/test/tests.rb @@ -0,0 +1,5 @@ +#!/usr/bin/ruby + +require_relative 'test_config.rb' +require_relative 'test_item.rb' +require_relative 'test_mpw.rb'