diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7622f..3e0c8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,30 +4,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [4.0.0-rc1] ### Added -- Options for Courier authlib authentication: courier_md5, courier_md5raw, courier_sha1, courier_sha256 -- crypt_type 'drupal' for Drupal 7 authentication +- New hashing algorithms: Argon2 Crypt (PHP 7.2 and above), Blowfish Crypt, Courier base64-encoded MD5, Courier base64-encoded SHA1, + Courier base64-encoded SHA256, Courier hexadecimal MD5, Extended DES Crypt, SHA256 Crypt, + SHA512 Crypt, SSHA512, Standard DES Crypt +- Option to allow users to change their display names +- Option to allow user to change its avatar +- Database query results cache +- Option for group display name +- Option for group is admin flag + +### Changed +- The whole core implementation, which is NOT COMPATIBLE with the previous versions. +- Minimum supported PHP version - 7.0 + +## Removed +- MySQL ENCRYPT() hashing implementation - Function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release. +- MySQL PASSWORD() hashing implementation - Function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release. +- Redmine hashing implementation - Cannot implement in new core system. +- User active column - Use database view instead +- Domain support ## [3.1.0] - 2018-02-06 ### Added - Column autocomplete for PostgreSQL - Currently supported parameters in README.md - SALT support for password algorithms "system" and "password_hash" + ### Changed - Updated README.me file - Nextcloud 12 & 13 support - Moved files to be more on the standard places - Renamed some files to be more standard like - Source code changes to be more standard like (max 80 characters) + ### Fixed - Column autocomplete in "Groups Settings" - Security fix for password length sniffing attacks - Small bug fixes + ## Removed - Code for supervisor mode -## [2.4.0] - 2017-12-26 +## 2.4.0 - 2017-12-26 ### Added - This CHANGELOG.md file - Support for PHP 7 @@ -37,3 +57,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Supported version of ownCloud, Nextcloud: ownCloud 10, Nextcloud 12 + +[4.0.0-rc1]: https://github.com/nextcloud/user_sql/compare/v3.1.0...v4.0.0-rc1 +[3.1.0]: https://github.com/nextcloud/user_sql/compare/v2.4.0...v3.1.0 diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..c7f159a --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,614 @@ +### GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +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 +them 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. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If 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 convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero 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 that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +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. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS 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. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. diff --git a/README.md b/README.md index 1ec67ec..2cdcfcf 100644 --- a/README.md +++ b/README.md @@ -3,98 +3,213 @@ user_sql **Nextcloud SQL user authentication.** -![](https://github.com/nextcloud/user_sql/blob/master/screenshot.png) +![screenshot](https://github.com/nextcloud/user_sql/blob/develop/img/screenshot.png) + +Use external database as a source for Nextcloud users and groups. +Retrieve the users and groups info. Allow the users to change their passwords. +Sync the users' email addresses with the addresses stored by Nextcloud. ## Getting Started -1. SSH into your server -2. Get into the apps folder of your Nextcloud installation, for example /var/www/nextcloud/apps +1. SSH into your server. -3. Git clone this project -``` -git clone https://github.com/nextcloud/user_sql.git -``` +2. Get into the apps folder of your Nextcloud installation, for example */var/www/nextcloud/apps*. + +3. Git clone this project: `git clone https://github.com/nextcloud/user_sql.git`. + +4. Login to your Nextcloud instance as admin. + +5. Navigate to Apps from the menu then find and enable the *User and Group SQL Backends* app. + +6. Navigate to Admin from menu and switch to Additional Settings, scroll down the page and you will see *SQL Backends* settings. + +*You can skip the first three steps as this app is available in the official [Nextcloud App Store](https://apps.nextcloud.com/apps/user_sql).* + +## Configuration + +Below are detailed descriptions of all available options. + +#### Database connection + +This section contains the database connection parameters. + +Name | Description | Details +--- | --- | --- +**SQL driver** | The database driver to use. Currently supported drivers are: mysql, pgsql. | Mandatory. +**Hostname** | The hostname on which the database server resides. | Mandatory. +**Database** | The name of the database. | Mandatory. +**Username** | The name of the user for the connection. | Optional. +**Password** | The password of the user for the connection. | Optional. + +#### Options + +Here are all currently supported options. + +Name | Description | Details +--- | --- | --- +**Allow display name change** | With this option enabled user can change its display name. The display name change is propagated to the database. | Optional.
Default: false.
Requires: user *Display name* column. +**Allow password change** | Can user change its password. The password change is propagated to the database. See [Hash algorithms](#hash-algorithms). | Optional.
Default: false. +**Use cache** | Use database query results cache. The cache can be cleared any time with the *Clear cache* button click. | Optional.
Default: false. +**Hashing algorithm** | How users passwords are stored in the database. See [Hash algorithms](#hash-algorithms). | Mandatory. +**Email sync** | Sync e-mail address with the Nextcloud.
- *None* - Disables this feature. This is the default option.
- *Synchronise only once* - Copy the e-mail address to the Nextcloud storage if its not set.
- *Nextcloud always wins* - Always copy the e-mail address to the database. This updates the user table.
- *SQL always wins* - Always copy the e-mail address to the Nextcloud storage. | Optional.
Default: *None*.
Requires: user *Email* column. +**Home mode** | User storage path.
- *Default* - Let the Nextcloud manage this. The default option.
- *Query* - Use location from the user table pointed by the *home* column.
- *Static* - Use static location. The `%u` variable is replaced with the username of the user. | Optional
Default: *Default*. +**Home Location** | User storage path for the `static` *home mode*. | Mandatory if the *Home mode* is set to `Static`. + +#### User table + +The definition of user table. The table containing user accounts. + +Name | Description | Details +--- | --- | --- +**Table name** | The table name. | Mandatory for user backend. +**Username** | Username column. | Mandatory for user backend. +**Email** | E-mail column. | Mandatory for *Email sync* option. +**Home** | Home path column. | Mandatory for `Query` *Home sync* option. +**Password** | Password hash column. | Mandatory for user backend. +**Display name** | Display name column. | Optional. +**Can change avatar** | Flag indicating if user can change its avatar. | Optional.
Default: false. + +#### Group table + +The group definitions table. -4. Login your Nextcloud as admin +Name | Description | Details +--- | --- | --- +**Table name** | The table name. | Mandatory for group backend. +**Is admin** | Flag indicating if its the admin group | Optional. +**Display name** | Display name column. | Optional. +**Group name** | Group name column. | Mandatory for group backend. -5. Navigate to Apps from the menu and enable the SQL user backend +#### User group table -6. Navigate to Admin from menu and switch to Additional Settings, scroll down the page and you will see SQL User Backend settings +Associative table which maps users to groups. + +Name | Description | Details +--- | --- | --- +**Table name** | The table name. | Mandatory for group backend. +**Username** | Username column. | Mandatory for group backend. +**Group name** | Group name column. | Mandatory for group backend. ## Integrations -### WordPress +The basic functionality requires only one database table: [User table](#user-table). + +For all options to work three tables are required: + - [User table](#user-table), + - [Group table](#group-table), + - [User group table](#user-group-table). + +If you already have an existing database you can always create database views which fits this model, +but be aware that some functionalities requires data changes (update queries). + +If you don't have any database model yet you can use below tables (MySQL): +``` +CREATE TABLE sql_users +( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(16) NOT NULL, + display_name TEXT NULL, + email TEXT NULL, + home TEXT NULL, + password TEXT NOT NULL, + can_change_avatar BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT users_username_uindex UNIQUE (username) +); + +CREATE TABLE sql_group +( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(16) NOT NULL, + display_name TEXT NULL, + admin BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT group_name_uindex UNIQUE (name) +); + +CREATE TABLE sql_user_group +( + id INT AUTO_INCREMENT PRIMARY KEY, + group_name VARCHAR(16) NOT NULL, + username VARCHAR(16) NOT NULL, + CONSTRAINT user_group_group_name_username_uindex UNIQUE (group_name, username), + INDEX user_group_group_name_index (group_name), + INDEX user_group_username_index (username) +); +``` + +#### WordPress + Thanks to this app, Nextcloud can easily integrate with Wordpress. -In the Nextcloud Column Settings of SQL User Backend, configure it as +In the Nextcloud user table settings of SQL Backends, configure it as: ``` -Table: wp_users -Username Column: user_login -Password Column: user_pass -Encryption Type: Joomla > 2.5.18 phppass +User table: wp_users +Username column: user_login +Password column: user_pass + +Hashing algorithm: Unix (Crypt) ``` -### JHipster -It is very easy to integrate Nextcloud with JHipster. +#### JHipster -Follow the Using the Database instructions in [Using Jhipster in development](http://www.jhipster.tech/development/) to configure your database. Assume you chose MySQL as JHipster database. +It is very easy to integrate Nextcloud with JHipster. -In the Nextcloud Column Settings of SQL User Backend, configure it as +Follow the Using the Database instructions in [Using Jhipster in development](http://www.jhipster.tech/development/) +to configure your database. Assume you chose MySQL as JHipster database. +In the Nextcloud user table settings of SQL Backends, configure it as: ``` -Table: jhi_users -Username Column: login -Password Column: password_hash -Encryption Type: Joomla > 2.5.18 phppass -User Activate Column: activated -Email Column: email +User table: jhi_users +Username column: login +Password column: password_hash +Email column: email + +Hashing algorithm: Unix (Crypt) ``` -## Features -Currently, it supports most of postfixadmin's encryption options, except dovecot and saslauthd. -It was tested and developed for a postfixadmin database. - -Password changing is disabled by default, but can be enabled in the Admin area. -Caution: user_sql does not recreate password salts, which imposes a security risk. -Password salts should be newly generated whenever the password changes. - -The column autocomplete works only for MySQL and PostgreSQL database which is used to validate form data. -If you use other database use *occ* command to set the application config parameters with domain suffix. - -For example to set 'sql_hostname' parameter in default domain use: - -```occ config:app:set user_sql 'sql_hostname_default' --value='localhost'``` - -### Currently supported parameters - -- sql_hostname -- sql_username -- sql_password -- sql_database -- sql_table -- sql_driver -- col_username -- col_password -- col_active -- col_displayname -- col_email -- col_gethome -- set_active_invert -- set_allow_pwchange -- set_default_domain -- set_strip_domain -- set_crypt_type -- set_mail_sync_mode -- set_enable_gethome -- set_gethome_mode -- set_gethome -- sql_group_table -- col_group_username -- col_group_name - -## Acknowledgments +## Hash algorithms + +Below is a table containing all of the supported hash implementations with example hashes. +The hashed password is "password", the salt if required have been generated randomly. + +Hash name | Details | Hash example value +--- | --- | --- +Cleartext | Never use this. Only for development. | password +Courier base64-encoded MD5 | No salt supported. | {MD5RAW}5f4dcc3b5aa765d61d8327deb882cf99 +Courier hexadecimal MD5 | No salt supported. | {MD5}X03MO1qnZdYdgyfeuILPmQ== +Courier base64-encoded SHA1 | No salt supported. | {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= +Courier base64-encoded SHA256 | No salt supported. | {SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg= +Unix (Crypt) | See [crypt](http://php.net/manual/en/function.crypt.php). | $2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq +Argon2 (Crypt) | Requires PHP >= 7.2.
Uses default parameters. See [password_hash](http://php.net/manual/en/function.password-hash.php). | $argon2i$v=19$m=1024,t=2,p=2$NnpSNlRNLlZobnJHUDh0Sw$oW5E1cfdPzLWfkTvQFUyzTR00R0aLwEdYwldcqW6Pmo +Blowfish (Crypt) | Uses default parameters. See [password_hash](http://php.net/manual/en/function.password-hash.php). | $2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq +Extended DES (Crypt) | | ..UZoIyj/Hy/c +MD5 (Crypt) | | $1$RzaFbNcU$u9adfTY/Q6za6nu0Ogrl1/ +SHA256 (Crypt) | Generates hash with 5000 rounds. | $5$rounds=5000$VIYD0iHkg7uY9SRc$v2XLS/9dvfFN84mzGvW9wxnVt9Xd/urXaaTkpW8EwD1 +SHA512 (Crypt) | Generates hash with 5000 rounds. | $6$rounds=5000$yH.Q0OL4qbCOUJ3q$Xry5EVFva3wKnfo8/ktrugmBd8tcl34NK6rXInv1HhmdSUNLEm0La9JnA57rqwQ.9/Bz513MD4tvmmISLUIHs/ +Standard DES (Crypt) | | yTBnb7ab/N072 +Joomla MD5 Encryption | Generates 32 chars salt. | 14d21b49b0f13e2acba962b6b0039edd:haJK0yTvBXTNMh76xwEw5RYEVpJsN8us +MD5 | No salt supported. | 5f4dcc3b5aa765d61d8327deb882cf99 +SHA1 | No salt supported. | 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 +SSHA256 | Generates 32 chars salt. | {SSHA256}+WxTB3JxprNteeovsuSYtgI+UkVPA9lfwGoYkz3Ff7hjd1FSdmlTMkNsSExyR21KM3NvNTZ5V0p4WXJMUjFzUg== +SSHA512 | Generates 32 chars salt. | {SSHA512}It+v1kAEUBbhMJYJ2swAtz+RLE6ispv/FB6G/ALhK/YWwEmrloY+0jzrWIfmu+rWUXp8u0Tg4jLXypC5oXAW00IyYnRVdEZJbE9wak96bkNRVWFCYmlJNWxrdTA0QmhL + +## Development + +#### New database driver support + +Add a new class in the `OCA\UserSQL\Platform` namespace which extends the `AbstractPlatform` class. +Add this driver in `admin.php` template to `$drivers` variable and in method `getPlatform(Connection $connection)` +of `PlatformFactory` class. + +#### New hashing algorithm support + +Create a new class in `OCA\UserSQL\Crypto` namespace which implements `IPasswordAlgorithm` interface. +Do not forget to write unit tests. + +### Acknowledgments + This repository contains continuation of work done in [this repo](https://www.aboehler.at/hg/user_sql/). +This plugin was heavily based on user_imap, user_pwauth, user_ldap and user_redmine! -This plugin is heavily based on user_imap, user_pwauth, user_ldap and user_redmine! +Since version 4.0.0 the whole core implementation has been rewritten. ### Credits diff --git a/ajax/settings.php b/ajax/settings.php deleted file mode 100644 index 77de46f..0000000 --- a/ajax/settings.php +++ /dev/null @@ -1,286 +0,0 @@ - - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see . - * - */ - - /** - * This is the AJAX portion of the settings page. - * - * It can: - * - Verify the connection settings - * - Load autocomplete values for tables - * - Load autocomplete values for columns - * - Save settings for a given domain - * - Load settings for a given domain - * - * It always returns JSON encoded responses - */ - -namespace OCA\user_sql; - -// Init owncloud - -// Check if we are a user -\OCP\User::checkAdminUser(); -\OCP\JSON::checkAppEnabled('user_sql'); - -// CSRF checks -\OCP\JSON::callCheck(); - - -$helper = new \OCA\user_sql\lib\Helper; - -$l = \OC::$server->getL10N('user_sql'); - -$params = $helper -> getParameterArray(); -$response = new \OCP\AppFramework\Http\JSONResponse(); - -// Check if the request is for us -if(isset($_POST['appname']) && ($_POST['appname'] === 'user_sql') && isset($_POST['function']) && isset($_POST['domain'])) -{ - $domain = $_POST['domain']; - switch($_POST['function']) - { - // Save the settings for the given domain to the database - case 'saveSettings': - $parameters = array('host' => $_POST['sql_hostname'], - 'password' => $_POST['sql_password'], - 'user' => $_POST['sql_username'], - 'dbname' => $_POST['sql_database'], - 'tablePrefix' => '' - ); - - // Check if the table exists - if(!$helper->verifyTable($parameters, $_POST['sql_driver'], $_POST['sql_table'])) - { - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('The selected SQL table '.$_POST['sql_table'].' does not exist!')))); - break; - } - if(!empty($_POST['sql_group_table']) && !$helper->verifyTable($parameters, $_POST['sql_driver'], $_POST['sql_group_table'])) - { - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('The selected SQL table '.$_POST['sql_group_table'].' does not exist!')))); - break; - } - - // Retrieve all column settings - $columns = array(); - $group_columns = array(); - foreach($params as $param) - { - if(strpos($param, 'col_') === 0) - { - if(isset($_POST[$param]) && $_POST[$param] !== '') - { - if(strpos($param, 'col_group_') === 0) - { - $group_columns[] = $_POST[$param]; - } - else - { - $columns[] = $_POST[$param]; - } - } - } - } - - // Check if the columns exist - $status = $helper->verifyColumns($parameters, $_POST['sql_driver'], $_POST['sql_table'], $columns); - if(!empty($_POST['sql_group_table']) && $status === true) - { - $status = $helper->verifyColumns($parameters, $_POST['sql_driver'], $_POST['sql_group_table'], $group_columns); - } - if($status !== true) - { - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('The selected SQL column(s) do(es) not exist: '.$status)))); - break; - } - - // If we reach this point, all settings have been verified - foreach($params as $param) - { - // Special handling for checkbox fields - if(isset($_POST[$param])) - { - if($param === 'set_strip_domain') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_strip_domain_'.$domain, 'true'); - } - elseif($param === 'set_allow_pwchange') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_allow_pwchange_'.$domain, 'true'); - } - elseif($param === 'set_active_invert') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_active_invert_'.$domain, 'true'); - } - elseif($param === 'set_enable_gethome') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_enable_gethome_'.$domain, 'true'); - } - else - { - \OC::$server->getConfig()->setAppValue('user_sql', $param.'_'.$domain, $_POST[$param]); - } - } else - { - if($param === 'set_strip_domain') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_strip_domain_'.$domain, 'false'); - } - elseif($param === 'set_allow_pwchange') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_allow_pwchange_'.$domain, 'false'); - } - elseif($param === 'set_active_invert') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_active_invert_'.$domain, 'false'); - } - elseif($param === 'set_enable_gethome') - { - \OC::$server->getConfig()->setAppValue('user_sql', 'set_enable_gethome_'.$domain, 'false'); - } - } - } - $response->setData(array('status' => 'success', - 'data' => array('message' => $l -> t('Application settings successfully stored.')))); - break; - - // Load the settings for a given domain - case 'loadSettingsForDomain': - $retArr = array(); - foreach($params as $param) - { - $retArr[$param] = \OC::$server->getConfig()->getAppValue('user_sql', $param.'_'.$domain, ''); - } - $response->setData(array('status' => 'success', - 'settings' => $retArr)); - break; - - // Try to verify the database connection settings - case 'verifySettings': - $cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig()); - - if(!isset($_POST['sql_driver'])) - { - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('Error connecting to database: No driver specified.')))); - break; - } - - if(($_POST['sql_hostname'] === '') || ($_POST['sql_database'] === '')) - { - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('Error connecting to database: You must specify at least host and database')))); - break; - } - - $parameters = array('host' => $_POST['sql_hostname'], - 'password' => $_POST['sql_password'], - 'user' => $_POST['sql_username'], - 'dbname' => $_POST['sql_database'], - 'tablePrefix' => '' - ); - - try { - $conn = $cm -> getConnection($_POST['sql_driver'], $parameters); - $response->setData(array('status' => 'success', - 'data' => array('message' => $l -> t('Successfully connected to database')))); - } - catch(\Exception $e) - { - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('Error connecting to database: ').$e->getMessage()))); - } - break; - - // Get the autocompletion values for a column - case 'getColumnAutocomplete': - - - $parameters = array('host' => $_POST['sql_hostname'], - 'password' => $_POST['sql_password'], - 'user' => $_POST['sql_username'], - 'dbname' => $_POST['sql_database'], - 'tablePrefix' => '' - ); - - if ($_POST['groupTable'] === 'true') { - $sql_table = $_POST['sql_group_table']; - } else { - $sql_table = $_POST['sql_table']; - } - - if($helper->verifyTable($parameters, $_POST['sql_driver'], $_POST['sql_table'])) - $columns = $helper->getColumns($parameters, $_POST['sql_driver'], $sql_table); - else - $columns = array(); - - $search = $_POST['request']; - $ret = array(); - - foreach($columns as $name) - { - if(($search === '') || ($search === 'search') || (strpos($name, $search) === 0)) - { - $ret[] = array('label' => $name, - 'value' => $name); - } - } - $response -> setData($ret); - break; - - // Get the autocompletion values for a table - case 'getTableAutocomplete': - $parameters = array('host' => $_POST['sql_hostname'], - 'password' => $_POST['sql_password'], - 'user' => $_POST['sql_username'], - 'dbname' => $_POST['sql_database'], - 'tablePrefix' => '' - ); - - $tables = $helper->getTables($parameters, $_POST['sql_driver']); - - $search = $_POST['request']; - $ret = array(); - foreach($tables as $name) - { - if(($search === '') || ($search === 'search') || (strpos($name, $search) === 0)) - { - $ret[] = array('label' => $name, - 'value' => $name); - } - } - $response -> setData($ret); - break; - } - -} else -{ - // If the request was not for us, set an error message - $response->setData(array('status' => 'error', - 'data' => array('message' => $l -> t('Not submitted for us.')))); -} - -// Return the JSON array -echo $response->render(); diff --git a/appinfo/app.php b/appinfo/app.php index df34741..716393f 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -1,31 +1,31 @@ -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see . -* -*/ + * Nextcloud - user_sql + * + * @copyright 2012-2015 Andreas Böhler + * @copyright 2018 Marcin Łojewski + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ -require_once(__DIR__ . '/../lib/user_sql.php'); -require_once __DIR__ . '/../lib/group_sql.php'; -$backend = new \OCA\user_sql\OC_USER_SQL; -$group_backend = new \OCA\user_sql\OC_GROUP_SQL; +use OCA\UserSQL\AppInfo\Application; +use OCP\AppFramework\QueryException; -\OC::$server->getUserManager()->registerBackend($backend); -\OC::$server->getGroupManager()->addBackend($group_backend); -?> +try { + $app = new Application(); + $app->registerBackends(); +} catch (QueryException $queryException) { + OC::$server->getLogger()->logException($queryException); +} diff --git a/appinfo/info.xml b/appinfo/info.xml index 1c5d6bf..66b9100 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -1,27 +1,31 @@ - user_sql - SQL user backend - Authenticate Users by SQL - Authenticate Users by SQL - 3.1.0 - agpl - Andreas Boehler <dev (at) aboehler (dot) at > - user_sql - https://github.com/nextcloud/user_sql/issues - https://github.com/nextcloud/user_sql - https://raw.githubusercontent.com/nextcloud/user_sql/v2.4.0/screenshot.png - - - - auth - - - mysql - pgsql - - - \OCA\user_sql\Settings\Admin - OCA\user_sql\Settings\Section - + user_sql + User and Group SQL Backends + Control users and groups by SQL queries + + Use external database as a source for Nextcloud users and groups. + Retrieve the users and groups info. Allow the users to change their passwords. + Sync the users' email addresses with the addresses stored by Nextcloud. + + 4.0.0-rc1 + agpl + Andreas Böhler <dev (at) aboehler (dot) at> + Marcin Łojewski <dev@mlojewski.me> + UserSQL + https://github.com/nextcloud/user_sql/issues + https://github.com/nextcloud/user_sql + https://raw.githubusercontent.com/nextcloud/user_sql/master/img/screenshot.png + + + + auth + + + + + + \OCA\UserSQL\Settings\Admin + OCA\UserSQL\Settings\Section + diff --git a/appinfo/routes.php b/appinfo/routes.php index afb4fea..8224fa4 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -1,8 +1,66 @@ -* This file is licensed under the Affero General Public License version 3 or later. -* See the COPYING-README file. -*/ -/** @var $this \OCP\Route\IRouter */ -$this->create('user_sql_ajax_settings', 'ajax/settings.php')->actionInclude('user_sql/ajax/settings.php'); + * Nextcloud - user_sql + * + * @copyright 2012-2015 Andreas Böhler + * @copyright 2018 Marcin Łojewski + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use OCA\UserSQL\AppInfo\Application; + +$application = new Application(); +$application->registerRoutes( + $this, [ + "routes" => [ + [ + "name" => "settings#verifyDbConnection", + "url" => "/settings/db/verify", + "verb" => "POST" + ], + [ + "name" => "settings#saveProperties", + "url" => "/settings/properties", + "verb" => "POST" + ], + [ + "name" => "settings#clearCache", + "url" => "/settings/cache/clear", + "verb" => "POST" + ], + [ + "name" => "settings#tableAutocomplete", + "url" => "/settings/autocomplete/table", + "verb" => "POST" + ], + [ + "name" => "settings#userTableAutocomplete", + "url" => "/settings/autocomplete/table/user", + "verb" => "POST" + ], + [ + "name" => "settings#userGroupTableAutocomplete", + "url" => "/settings/autocomplete/table/user_group", + "verb" => "POST" + ], + [ + "name" => "settings#groupTableAutocomplete", + "url" => "/settings/autocomplete/table/group", + "verb" => "POST" + ], + ] + ] +); diff --git a/appinfo/update.php b/appinfo/update.php deleted file mode 100644 index 6312a16..0000000 --- a/appinfo/update.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see . - * - */ - -$installedVersion = \OC::$server->getConfig()->getAppValue('user_sql', 'installed_version'); - -$params = array('sql_host' => 'sql_hostname', - 'sql_user' => 'sql_username', - 'sql_database' => 'sql_database', - 'sql_password' => 'sql_password', - 'sql_table' => 'sql_table', - 'sql_column_username' => 'col_username', - 'sql_column_password' => 'col_password', - 'sql_type' => 'sql_driver', - 'sql_column_active' => 'col_active', - 'strip_domain' => 'set_strip_domain', - 'default_domain' => 'set_default_domain', - 'crypt_type' => 'set_crypt_type', - 'sql_column_displayname' => 'col_displayname', - 'allow_password_change' => 'set_allow_pwchange', - 'sql_column_active_invert' => 'set_active_invert', - 'sql_column_email' => 'col_email', - 'mail_sync_mode' => 'set_mail_sync_mode' - ); - -$delParams = array('domain_settings', - 'map_array', - 'domain_array' - ); - -if(version_compare($installedVersion, '1.99', '<')) -{ - foreach($params as $oldPar => $newPar) - { - $val = \OC::$server->getConfig()->getAppValue('user_sql', $oldPar); - if(($oldPar === 'strip_domain') || ($oldPar === 'allow_password_change') || ($oldPar === 'sql_column_active_invert')) - { - if($val) - $val = 'true'; - else - $val = 'false'; - } - if($val) - \OC::$server->getConfig()->setAppValue('user_sql', $newPar.'_default', $val); - \OC::$server->getConfig()->deleteAppValue('user_sql', $oldPar); - } - - foreach($delParams as $param) - { - \OC::$server->getConfig()->deleteAppValue('user_sql', $param); - } -} diff --git a/css/settings.css b/css/settings.css index 87f19b7..33f454a 100644 --- a/css/settings.css +++ b/css/settings.css @@ -1,26 +1,58 @@ -.statusmessage { - background-color: #DDDDFF; +#user_sql .main { + overflow: auto; } -.errormessage { - background-color: #FFDDDD; + +#user_sql .main > div { + float: left; + width: 380px; +} + +#user_sql .main div > label > span { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + width: 120px; } -.successmessage { - background-color: #DDFFDD; + +#user_sql .main div > label > input, +#user_sql .main div > label > select { + width: 257px; } -.statusmessage,.errormessage,.successmessage{ - display:none; - padding: 1px; + +#user_sql .main div > input[type="checkbox"] { + min-height: auto; } -#sql-1 p label:first-child, -#sql-2 p label:first-child, -#sql-3 p label:first-child, -#sql-4 p label:first-child, -#sql-5 p label:first-child, -#sql-6 p label:first-child, -#sql-7 p label:first-child { - display: inline-block; - text-align: right; - width: 300px; - padding-right: 10px; +#user_sql .main .button-right { + overflow: auto; +} + +#user_sql .main .button-right > input[type="submit"] { + float: right; +} + +#user_sql .msg { + left: 0; + padding: 3px; + position: fixed; + text-align: center; + width: 100%; + z-index: 100; +} + +#user_sql .msg.error { + background-color: #d2322d; + color: #fff; +} + +#user_sql .msg.success { + background-color: #47a447; + color: #fff; } + +#user_sql .msg.waiting { + background-color: #ff8f00; + color: #fff; +} \ No newline at end of file diff --git a/img/app-dark.svg b/img/app-dark.svg index 54939fa..f6c7cf3 100644 --- a/img/app-dark.svg +++ b/img/app-dark.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/img/screenshot.png b/img/screenshot.png new file mode 100644 index 0000000..d47dbd8 Binary files /dev/null and b/img/screenshot.png differ diff --git a/js/settings.js b/js/settings.js index f9f6b44..0098601 100644 --- a/js/settings.js +++ b/js/settings.js @@ -1,358 +1,98 @@ -// settings.js of user_sql +var user_sql = user_sql || {}; +var form_id = "#user_sql"; -// declare namespace -var user_sql = user_sql || -{ -}; - -/** - * init admin settings view - */ -user_sql.adminSettingsUI = function() -{ - - if($('#sqlDiv').length > 0) - { - // enable tabs on settings page - $('#sqlDiv').tabs(); - - // Attach auto-completion to all column fields - $('#col_username, #col_password, #col_displayname, #col_active, #col_email, #col_gethome').autocomplete({ - source: function(request, response) - { - var post = $('#sqlForm').serializeArray(); - var domain = $('#sql_domain_chooser option:selected').val(); - - post.push({ - name: 'function', - value: 'getColumnAutocomplete' - }); - - post.push({ - name: 'domain', - value: domain - }); - - post.push({ - name: 'request', - value: request.term - }); - - // Ajax foobar - $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, response, 'json'); - }, - minLength: 0, - open: function() { - $(this).attr('state', 'open'); - }, - close: function() { - $(this).attr('state', 'closed'); - } - }).focus(function() { - if($(this).attr('state') != 'open') - { - $(this).autocomplete("search"); - } - }); +user_sql.adminSettingsUI = function () { + var app_id = "user_sql"; - // Attach auto-completion to all group column fields - $('#col_group_name, #col_group_username').autocomplete({ - source: function(request, response) - { - var post = $('#sqlForm').serializeArray(); - var domain = $('#sql_domain_chooser option:selected').val(); + if ($(form_id).length > 0) { - post.push({ - name: 'groupTable', - value: 'true' - }); + var click = function (event, path) { + event.preventDefault(); - post.push({ - name: 'function', - value: 'getColumnAutocomplete' - }); + var post = $(form_id).serializeArray(); + var msg = $("#user_sql-msg"); + var msg_body = $("#user_sql-msg-body"); - post.push({ - name: 'domain', - value: domain - }); + msg_body.html(t(app_id, "Waiting...")); + msg.addClass("waiting"); + msg.slideDown(); - post.push({ - name: 'request', - value: request.term - }); + $.post(OC.generateUrl(path), post, function (data) { + msg_body.html(data.data.message); + msg.removeClass("error"); + msg.removeClass("success"); + msg.removeClass("waiting"); - // Ajax foobar - $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, response, 'json'); - }, - minLength: 0, - open: function() { - $(this).attr('state', 'open'); - }, - close: function() { - $(this).attr('state', 'closed'); - } - }).focus(function() { - if($(this).attr('state') != 'open') - { - $(this).autocomplete("search"); - } - }); - - // Attach auto-completion to all table fields - $('#sql_table, #sql_group_table').autocomplete({ - source: function(request, response) - { - var post = $('#sqlForm').serializeArray(); - var domain = $('#sql_domain_chooser option:selected').val(); - - post.push({ - name: 'function', - value: 'getTableAutocomplete' - }); - - post.push({ - name: 'domain', - value: domain - }); - - post.push({ - name: 'request', - value: request.term - }); - - // Ajax foobar - $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, response, 'json'); - }, - minLength: 0, - open: function() { - $(this).attr('state', 'open'); - }, - close: function() { - $(this).attr('state', 'closed'); - } - }).focus(function() { - if($(this).attr('state') != 'open') - { - $(this).autocomplete("search"); - } - }); - - // Verify the SQL database settings - $('#sqlVerify').click(function(event) - { - event.preventDefault(); - - var post = $('#sqlForm').serializeArray(); - var domain = $('#sql_domain_chooser option:selected').val(); - - post.push({ - name: 'function', - value: 'verifySettings' - }); - - post.push({ - name: 'domain', - value: domain - }); - - $('#sql_verify_message').show(); - $('#sql_success_message').hide(); - $('#sql_error_message').hide(); - $('#sql_update_message').hide(); - // Ajax foobar - $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, function(data) - { - $('#sql_verify_message').hide(); - if(data.status == 'success') - { - $('#sql_success_message').html(data.data.message); - $('#sql_success_message').show(); - window.setTimeout(function() - { - $('#sql_success_message').hide(); - }, 10000); - } else - { - $('#sql_error_message').html(data.data.message); - $('#sql_error_message').show(); + if (data.status === "success") { + msg.addClass("success"); + } else { + msg.addClass("error"); } - }, 'json'); - return false; - }); - // Save the settings for a domain - $('#sqlSubmit').click(function(event) - { - event.preventDefault(); + window.setTimeout(function () { + msg.slideUp(); + }, 10000); + }, "json"); - var post = $('#sqlForm').serializeArray(); - var domain = $('#sql_domain_chooser option:selected').val(); - - post.push({ - name: 'function', - value: 'saveSettings' - }); - - post.push({ - name: 'domain', - value: domain + return false; + }; + + var autocomplete = function (ids, path) { + $(ids).autocomplete({ + source: function (request, response) { + var post = $(form_id).serializeArray(); + $.post(OC.generateUrl(path), post, response, "json"); + }, + minLength: 0, + open: function () { + $(this).attr("state", "open"); + }, + close: function () { + $(this).attr("state", "closed"); + } + }).focus(function () { + if ($(this).attr("state") !== "open") { + $(this).autocomplete("search"); + } }); + }; - $('#sql_update_message').show(); - $('#sql_success_message').hide(); - $('#sql_verify_message').hide(); - $('#sql_error_message').hide(); - // Ajax foobar - $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, function(data) - { - $('#sql_update_message').hide(); - if(data.status == 'success') - { - $('#sql_success_message').html(data.data.message); - $('#sql_success_message').show(); - window.setTimeout(function() - { - $('#sql_success_message').hide(); - }, 10000); - } else - { - $('#sql_error_message').html(data.data.message); - $('#sql_error_message').show(); - } - }, 'json'); - return false; + $("#user_sql-db_connection_verify").click(function (event) { + return click(event, "/apps/user_sql/settings/db/verify"); }); - // Attach event handler to the domain chooser - $('#sql_domain_chooser').change(function() { - user_sql.loadDomainSettings($('#sql_domain_chooser option:selected').val()); - }); - - $('#set_gethome_mode').change(function() { - user_sql.setGethomeMode(); + $("#user_sql-clear_cache").click(function (event) { + return click(event, "/apps/user_sql/settings/cache/clear"); }); - - $('#set_enable_gethome').change(function() { - user_sql.setGethomeMode(); + + $("#user_sql-save").click(function (event) { + return click(event, "/apps/user_sql/settings/properties"); }); - } -}; -user_sql.setGethomeMode = function() -{ - var enabled = $('#set_enable_gethome').prop('checked'); - if(enabled) - { - $('#set_gethome_mode').prop('disabled', false); - var val = $('#set_gethome_mode option:selected').val(); - if(val === 'query') - { - $('#set_gethome').prop('disabled', true); - $('#col_gethome').prop('disabled', false); - } - else if(val === 'static') - { - $('#set_gethome').prop('disabled', false); - $('#col_gethome').prop('disabled', true); - } - else - { - $('#set_gethome').prop('disabled', true); - $('#col_gethome').prop('disabled', true); - } - } - else - { - $('#set_gethome_mode').prop('disabled', true); - $('#set_gethome').prop('disabled', true); - $('#col_gethome').prop('disabled', true); + autocomplete( + "#db-table-user, #db-table-user_group, #db-table-group", + "/apps/user_sql/settings/autocomplete/table" + ); + + autocomplete( + "#db-table-user-column-uid, #db-table-user-column-email, #db-table-user-column-home, #db-table-user-column-password, #db-table-user-column-name, #db-table-user-column-avatar", + "/apps/user_sql/settings/autocomplete/table/user" + ); + + autocomplete( + "#db-table-user_group-column-uid, #db-table-user_group-column-gid", + "/apps/user_sql/settings/autocomplete/table/user_group" + ); + + autocomplete( + "#db-table-group-column-admin, #db-table-group-column-name, #db-table-group-column-gid", + "/apps/user_sql/settings/autocomplete/table/group" + ); } }; -/** - * Load the settings for the selected domain - * @param string domain The domain to load - */ -user_sql.loadDomainSettings = function(domain) -{ - $('#sql_success_message').hide(); - $('#sql_error_message').hide(); - $('#sql_verify_message').hide(); - $('#sql_loading_message').show(); - var post = [ - { - name: 'appname', - value: 'user_sql' - }, - { - name: 'function', - value: 'loadSettingsForDomain' - }, - { - name: 'domain', - value: domain - } - ]; - $.post(OC.filePath('user_sql', 'ajax', 'settings.php'), post, function(data) - { - $('#sql_loading_message').hide(); - if(data.status == 'success') - { - for(key in data.settings) - { - if(key == 'set_strip_domain') - { - if(data.settings[key] == 'true') - $('#' + key).prop('checked', true); - else - $('#' + key).prop('checked', false); - } - else if(key == 'set_allow_pwchange') - { - if(data.settings[key] == 'true') - $('#' + key).prop('checked', true); - else - $('#' + key).prop('checked', false); - } - else if(key == 'set_active_invert') - { - if(data.settings[key] == 'true') - $('#' + key).prop('checked', true); - else - $('#' + key).prop('checked', false); - } - else if(key == 'set_enable_gethome') - { - if(data.settings[key] == 'true') - $('#' + key).prop('checked', true); - else - $('#' + key).prop('checked', false); - } - else - { - $('#' + key).val(data.settings[key]); - } - } - } - else - { - $('#sql_error_message').html(data.data.message); - $('#sql_error_message').show(); - } - user_sql.setGethomeMode(); - }, 'json' - ); -}; - -// Run our JS if the SQL settings are present -$(document).ready(function() -{ - if($('#sqlDiv')) - { +$(document).ready(function () { + if ($(form_id)) { user_sql.adminSettingsUI(); - user_sql.loadDomainSettings($('#sql_domain_chooser option:selected').val()); - user_sql.setGethomeMode(); } -}); - +}); \ No newline at end of file diff --git a/lib/Action/EmailSync.php b/lib/Action/EmailSync.php new file mode 100644 index 0000000..3c9bc79 --- /dev/null +++ b/lib/Action/EmailSync.php @@ -0,0 +1,137 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Action; + +use OCA\UserSQL\Constant\App; +use OCA\UserSQL\Constant\Opt; +use OCA\UserSQL\Model\User; +use OCA\UserSQL\Properties; +use OCA\UserSQL\Repository\UserRepository; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Synchronizes the user email address. + * + * @author Marcin Łojewski + */ +class EmailSync implements IUserAction +{ + /** + * @var string The application name. + */ + private $appName; + /** + * @var ILogger The logger instance. + */ + private $logger; + /** + * @var Properties The properties array. + */ + private $properties; + /** + * @var IConfig The config instance. + */ + private $config; + /** + * @var UserRepository The user repository. + */ + private $userRepository; + + /** + * The default constructor. + * + * @param string $appName The application name. + * @param ILogger $logger The logger instance. + * @param Properties $properties The properties array. + * @param IConfig $config The config instance. + * @param UserRepository $userRepository The user repository. + */ + public function __construct( + $appName, ILogger $logger, Properties $properties, IConfig $config, + UserRepository $userRepository + ) { + $this->appName = $appName; + $this->logger = $logger; + $this->properties = $properties; + $this->config = $config; + $this->userRepository = $userRepository; + } + + /** + * @inheritdoc + * @throws \OCP\PreConditionNotMetException + */ + public function doAction(User $user) + { + $this->logger->debug( + "Entering EmailSync#doAction($user->uid)", ["app" => $this->appName] + ); + + $ncMail = $this->config->getUserValue( + $user->uid, "settings", "email", "" + ); + + $result = false; + + switch ($this->properties[Opt::EMAIL_SYNC]) { + case App::EMAIL_INITIAL: + if (empty($ncMail) && !empty($user->email)) { + $this->config->setUserValue( + $user->uid, "settings", "email", $user->email + ); + } + + $result = true; + break; + case App::EMAIL_FORCE_NC: + if (!empty($ncMail) && $user->email !== $ncMail) { + $user = $this->userRepository->findByUid($user->uid); + if (!($user instanceof User)) { + break; + } + + $user->email = $ncMail; + $result = $this->userRepository->save($user); + } + + break; + case App::EMAIL_FORCE_SQL: + if (!empty($user->email) && $user->email !== $ncMail) { + $this->config->setUserValue( + $user->uid, "settings", "email", $user->email + ); + } + + $result = true; + break; + } + + $this->logger->debug( + "Returning EmailSync#doAction($user->uid): " . ($result ? "true" + : "false"), + ["app" => $this->appName] + ); + + return $result; + } +} diff --git a/lib/Action/IUserAction.php b/lib/Action/IUserAction.php new file mode 100644 index 0000000..46df9fe --- /dev/null +++ b/lib/Action/IUserAction.php @@ -0,0 +1,41 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Action; + +use OCA\UserSQL\Model\User; + +/** + * Action to execute every time an user account is queried. + * + * @author Marcin Łojewski + */ +interface IUserAction +{ + /** + * Execute an action. + * + * @param User $user The user entity. + * + * @return bool The action status. + */ + public function doAction(User $user); +} diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php new file mode 100644 index 0000000..730b953 --- /dev/null +++ b/lib/AppInfo/Application.php @@ -0,0 +1,67 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\AppInfo; + +use OCP\AppFramework\App; +use OCP\AppFramework\QueryException; + +/** + * The application bootstrap class. + * + * @author Marcin Łojewski + */ +class Application extends App +{ + /** + * The class constructor. + * + * @param array $urlParams An array with variables extracted + * from the routes. + */ + public function __construct(array $urlParams = array()) + { + parent::__construct("user_sql", $urlParams); + } + + /** + * Register the application backends + * if all necessary configuration is provided. + * + * @throws QueryException If the query container's could not be resolved + */ + public function registerBackends() + { + $userBackend = $this->getContainer()->query( + '\OCA\UserSQL\Backend\UserBackend' + ); + $groupBackend = $this->getContainer()->query( + '\OCA\UserSQL\Backend\GroupBackend' + ); + + if ($userBackend->isConfigured()) { + \OC::$server->getUserManager()->registerBackend($userBackend); + } + if ($groupBackend->isConfigured()) { + \OC::$server->getGroupManager()->addBackend($groupBackend); + } + } +} diff --git a/lib/Backend/GroupBackend.php b/lib/Backend/GroupBackend.php new file mode 100644 index 0000000..2ef5cd0 --- /dev/null +++ b/lib/Backend/GroupBackend.php @@ -0,0 +1,457 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Backend; + +use OC\Group\Backend; +use OCA\UserSQL\Cache; +use OCA\UserSQL\Constant\DB; +use OCA\UserSQL\Model\Group; +use OCA\UserSQL\Properties; +use OCA\UserSQL\Repository\GroupRepository; +use OCP\ILogger; + +/** + * The SQL group backend manager. + * + * @author Marcin Łojewski + */ +final class GroupBackend extends Backend +{ + /** + * @var string The application name. + */ + private $appName; + /** + * @var ILogger The logger instance. + */ + private $logger; + /** + * @var Cache The cache instance. + */ + private $cache; + /** + * @var GroupRepository The group repository. + */ + private $groupRepository; + /** + * @var Properties The properties array. + */ + private $properties; + + /** + * The default constructor. + * + * @param string $AppName The application name. + * @param Cache $cache The cache instance. + * @param ILogger $logger The logger instance. + * @param Properties $properties The properties array. + * @param GroupRepository $groupRepository The group repository. + */ + public function __construct( + $AppName, Cache $cache, ILogger $logger, Properties $properties, + GroupRepository $groupRepository + ) { + $this->appName = $AppName; + $this->cache = $cache; + $this->logger = $logger; + $this->properties = $properties; + $this->groupRepository = $groupRepository; + } + + /** + * @inheritdoc + */ + public function getGroups($search = "", $limit = null, $offset = null) + { + $this->logger->debug( + "Entering getGroups($search, $limit, $offset)", + ["app" => $this->appName] + ); + + $cacheKey = self::class . "groups_" . $search . "_" . $limit . "_" + . $offset; + $groups = $this->cache->get($cacheKey); + + if (!is_null($groups)) { + $this->logger->debug( + "Returning from cache getGroups($search, $limit, $offset): count(" + . count($groups) . ")", ["app" => $this->appName] + ); + return $groups; + } + + $groups = $this->groupRepository->findAllBySearchTerm( + "%" . $search . "%", $limit, $offset + ); + + if ($groups === false) { + return []; + } + + foreach ($groups as $group) { + $this->cache->set("group_" . $group->gid, $group); + } + + $groups = array_map( + function ($group) { + return $group->gid; + }, $groups + ); + + $this->cache->set($cacheKey, $groups); + $this->logger->debug( + "Returning getGroups($search, $limit, $offset): count(" . count( + $groups + ) . ")", ["app" => $this->appName] + ); + + return $groups; + } + + /** + * Returns the number of users in given group matching the search term. + * + * @param string $gid The group ID. + * @param string $search The search term. + * + * @return int The number of users in given group matching the search term. + */ + public function countUsersInGroup($gid, $search = "") + { + $this->logger->debug( + "Entering countUsersInGroup($gid, $search)", + ["app" => $this->appName] + ); + + $cacheKey = self::class . "users#_" . $gid . "_" . $search; + $count = $this->cache->get($cacheKey); + + if (!is_null($count)) { + $this->logger->debug( + "Returning from cache countUsersInGroup($gid, $search): $count", + ["app" => $this->appName] + ); + return $count; + } + + $count = $this->groupRepository->countAll($gid, "%" . $search . "%"); + + if ($count === false) { + return 0; + } + + $this->cache->set($cacheKey, $count); + $this->logger->debug( + "Returning countUsersInGroup($gid, $search): $count", + ["app" => $this->appName] + ); + + return $count; + } + + /** + * @inheritdoc + */ + public function inGroup($uid, $gid) + { + $this->logger->debug( + "Entering inGroup($uid, $gid)", ["app" => $this->appName] + ); + + $cacheKey = self::class . "user_group_" . $uid . "_" . $gid; + $inGroup = $this->cache->get($cacheKey); + + if (!is_null($inGroup)) { + $this->logger->debug( + "Returning from cache inGroup($uid, $gid): " . ($inGroup + ? "true" : "false"), ["app" => $this->appName] + ); + return $inGroup; + } + + $inGroup = in_array($gid, $this->getUserGroups($uid)); + + $this->cache->set($cacheKey, $inGroup); + $this->logger->debug( + "Returning inGroup($uid, $gid): " . ($inGroup ? "true" : "false"), + ["app" => $this->appName] + ); + + return $inGroup; + } + + /** + * @inheritdoc + */ + public function getUserGroups($uid) + { + $this->logger->debug( + "Entering getUserGroups($uid)", ["app" => $this->appName] + ); + + $cacheKey = self::class . "user_groups_" . $uid; + $groups = $this->cache->get($cacheKey); + + if (!is_null($groups)) { + $this->logger->debug( + "Returning from cache getUserGroups($uid): count(" . count( + $groups + ) . ")", ["app" => $this->appName] + ); + return $groups; + } + + $groups = $this->groupRepository->findAllByUid($uid); + + if ($groups === false) { + return []; + } + + foreach ($groups as $group) { + $this->cache->set("group_" . $group->gid, $group); + } + + $groups = array_map( + function ($group) { + return $group->gid; + }, $groups + ); + + $this->cache->set($cacheKey, $groups); + $this->logger->debug( + "Returning getUserGroups($uid): count(" . count( + $groups + ) . ")", ["app" => $this->appName] + ); + + return $groups; + } + + /** + * @inheritdoc + */ + public function groupExists($gid) + { + $this->logger->debug( + "Entering groupExists($gid)", ["app" => $this->appName] + ); + + $group = $this->getGroup($gid); + + if ($group === false) { + return false; + } + + $exists = !is_null($group); + $this->logger->debug( + "Returning groupExists($gid): " . ($exists ? "true" : "false"), + ["app" => $this->appName] + ); + + return $exists; + } + + /** + * Get a group entity object. If it's found value from cache is used. + * + * @param $gid $uid The group ID. + * + * @return Group The group entity, NULL if it does not exists or + * FALSE on failure. + */ + private function getGroup($gid) + { + $cacheKey = self::class . "group_" . $gid; + $cachedGroup = $this->cache->get($cacheKey); + + if (!is_null($cachedGroup)) { + if ($cachedGroup === false) { + $this->logger->debug( + "Found null group in cache: $gid", ["app" => $this->appName] + ); + return null; + } + + $group = new Group(); + foreach ($cachedGroup as $key => $value) { + $group->{$key} = $value; + } + + $this->logger->debug( + "Found group in cache: " . $group->gid, + ["app" => $this->appName] + ); + + return $group; + } + + $group = $this->groupRepository->findByGid($gid); + + if ($group instanceof Group) { + $this->cache->set($cacheKey, $group); + } elseif (is_null($group)) { + $this->cache->set($cacheKey, false); + } + + return $group; + } + + /** + * @inheritdoc + */ + public function usersInGroup($gid, $search = "", $limit = -1, $offset = 0) + { + $this->logger->debug( + "Entering usersInGroup($gid, $search, $limit, $offset)", + ["app" => $this->appName] + ); + + $cacheKey = self::class . "group_users_" . $gid . "_" . $search . "_" + . $limit . "_" . $offset; + $users = $this->cache->get($cacheKey); + + if (!is_null($users)) { + $this->logger->debug( + "Returning from cache usersInGroup($gid, $search, $limit, $offset): count(" + . count($users) . ")", ["app" => $this->appName] + ); + return $users; + } + + $uids = $this->groupRepository->findAllUidsBySearchTerm( + $gid, "%" . $search . "%", $limit, $offset + ); + + if ($uids === false) { + return []; + } + + $this->cache->set($cacheKey, $uids); + $this->logger->debug( + "Returning usersInGroup($gid, $search, $limit, $offset): count(" + . count($uids) . ")", ["app" => $this->appName] + ); + + return $uids; + } + + /** + * Checks if a user is in the admin group. + * + * @param string $uid User ID. + * + * @return bool TRUE if a user is in the admin group, FALSE otherwise. + */ + public function isAdmin($uid) + { + $this->logger->debug( + "Entering isAdmin($uid)", ["app" => $this->appName] + ); + + $cacheKey = self::class . "admin_" . $uid; + $admin = $this->cache->get($cacheKey); + + if (!is_null($admin)) { + $this->logger->debug( + "Returning from cache isAdmin($uid): " . ($admin ? "true" + : "false"), ["app" => $this->appName] + ); + return $admin; + } + + $admin = $this->groupRepository->belongsToAdmin($uid); + + if (is_null($admin)) { + return false; + } + + $this->cache->set($cacheKey, $admin); + $this->logger->debug( + "Returning isAdmin($uid): " . ($admin ? "true" : "false"), + ["app" => $this->appName] + ); + + return $admin; + } + + /** + * Get associative array of the group details. + * + * @param string $gid The group ID. + * + * @return array Associative array of the group details. + */ + public function getGroupDetails($gid) + { + $this->logger->debug( + "Entering getGroupDetails($gid)", ["app" => $this->appName] + ); + + $group = $this->getGroup($gid); + + if (!($group instanceof Group)) { + return []; + } + + $details = ["displayName" => $group->name]; + $this->logger->debug( + "Returning getGroupDetails($gid): " . implode(", ", $details), + ["app" => $this->appName] + ); + + return $details; + } + + /** + * @inheritdoc + */ + public function getSupportedActions() + { + $actions = parent::getSupportedActions(); + + $actions &= empty($this->properties[DB::GROUP_ADMIN_COLUMN]) + ? ~Backend::IS_ADMIN : ~0; + $actions &= empty($this->properties[DB::GROUP_NAME_COLUMN]) + ? ~Backend::GROUP_DETAILS : ~0; + + return $actions; + } + + /** + * Check if this backend is correctly set and can be enabled. + * + * @return bool TRUE if all necessary options for this backend + * are configured, FALSE otherwise. + */ + public function isConfigured() + { + return !empty($this->properties[DB::DATABASE]) + && !empty($this->properties[DB::DRIVER]) + && !empty($this->properties[DB::HOSTNAME]) + && !empty($this->properties[DB::USERNAME]) + && !empty($this->properties[DB::GROUP_TABLE]) + && !empty($this->properties[DB::USER_GROUP_TABLE]) + && !empty($this->properties[DB::GROUP_GID_COLUMN]) + && !empty($this->properties[DB::USER_GROUP_GID_COLUMN]) + && !empty($this->properties[DB::USER_GROUP_UID_COLUMN]); + } +} diff --git a/lib/Backend/UserBackend.php b/lib/Backend/UserBackend.php new file mode 100644 index 0000000..91e71a8 --- /dev/null +++ b/lib/Backend/UserBackend.php @@ -0,0 +1,567 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Backend; + +use OC\User\Backend; +use OCA\UserSQL\Action\EmailSync; +use OCA\UserSQL\Action\IUserAction; +use OCA\UserSQL\Cache; +use OCA\UserSQL\Constant\App; +use OCA\UserSQL\Constant\DB; +use OCA\UserSQL\Constant\Opt; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCA\UserSQL\Model\User; +use OCA\UserSQL\Properties; +use OCA\UserSQL\Repository\UserRepository; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; + +/** + * The SQL user backend manager. + * + * @author Marcin Łojewski + */ +final class UserBackend extends Backend +{ + /** + * @var string The application name. + */ + private $appName; + /** + * @var ILogger The logger instance. + */ + private $logger; + /** + * @var Cache The cache instance. + */ + private $cache; + /** + * @var UserRepository The user repository. + */ + private $userRepository; + /** + * @var Properties The properties array. + */ + private $properties; + /** + * @var IL10N The localization service. + */ + private $localization; + /** + * @var IConfig The config instance. + */ + private $config; + /** + * @var IUserAction[] The actions to execute. + */ + private $actions; + + /** + * The default constructor. + * + * @param string $AppName The application name. + * @param Cache $cache The cache instance. + * @param ILogger $logger The logger instance. + * @param Properties $properties The properties array. + * @param UserRepository $userRepository The user repository. + * @param IL10N $localization The localization service. + * @param IConfig $config The config instance. + */ + public function __construct( + $AppName, Cache $cache, ILogger $logger, Properties $properties, + UserRepository $userRepository, IL10N $localization, IConfig $config + ) { + $this->appName = $AppName; + $this->cache = $cache; + $this->logger = $logger; + $this->properties = $properties; + $this->userRepository = $userRepository; + $this->localization = $localization; + $this->config = $config; + $this->actions = []; + + $this->initActions(); + } + + /** + * Initiate the actions array. + */ + private function initActions() + { + if (!empty($this->properties[Opt::EMAIL_SYNC]) + && !empty($this->properties[DB::USER_EMAIL_COLUMN]) + ) { + $this->actions[] = new EmailSync( + $this->appName, $this->logger, $this->properties, $this->config, + $this->userRepository + ); + } + } + + /** + * @inheritdoc + */ + public function hasUserListings() + { + return true; + } + + /** + * Count users in the database. + * + * @return int The number of users. + */ + public function countUsers() + { + $this->logger->debug( + "Entering countUsers()", ["app" => $this->appName] + ); + + $cacheKey = self::class . "users#"; + $count = $this->cache->get($cacheKey); + + if (!is_null($count)) { + $this->logger->debug( + "Returning from cache countUsers(): $count", + ["app" => $this->appName] + ); + return $count; + } + + $count = $this->userRepository->countAll("%"); + + if ($count === false) { + return 0; + } + + $this->cache->set($cacheKey, $count); + $this->logger->debug( + "Returning countUsers(): $count", ["app" => $this->appName] + ); + + return $count; + } + + /** + * @inheritdoc + */ + public function userExists($uid) + { + $this->logger->debug( + "Entering userExists($uid)", ["app" => $this->appName] + ); + + $user = $this->getUser($uid); + + if ($user === false) { + return false; + } + + $exists = !is_null($user); + $this->logger->debug( + "Returning userExists($uid): " . ($exists ? "true" : "false"), + ["app" => $this->appName] + ); + + return $exists; + } + + /** + * Get a user entity object. If it's found value from cache is used. + * + * @param string $uid The user ID. + * + * @return User The user entity, NULL if it does not exists or + * FALSE on failure. + */ + private function getUser($uid) + { + $cacheKey = self::class . "user_" . $uid; + $cachedUser = $this->cache->get($cacheKey); + + if (!is_null($cachedUser)) { + $user = new User(); + foreach ($cachedUser as $key => $value) { + $user->{$key} = $value; + } + + $this->logger->debug( + "Found user in cache: " . $user->uid, ["app" => $this->appName] + ); + + return $user; + } + + $user = $this->userRepository->findByUid($uid); + + if ($user instanceof User) { + $this->cache->set($cacheKey, $user); + + foreach ($this->actions as $action) { + $action->doAction($user); + } + } + + return $user; + } + + /** + * @inheritdoc + */ + public function getDisplayName($uid) + { + $this->logger->debug( + "Entering getDisplayName($uid)", ["app" => $this->appName] + ); + + $user = $this->getUser($uid); + + if (!($user instanceof User)) { + return false; + } + + $name = $user->name; + $this->logger->debug( + "Returning getDisplayName($uid): $name", + ["app" => $this->appName] + ); + + return $name; + } + + /** + * Check if the user's password is correct then return its ID or + * FALSE on failure. + * + * @param string $uid The user ID. + * @param string $password The password. + * + * @return string|bool The user ID on success, false otherwise. + */ + public function checkPassword($uid, $password) + { + $this->logger->debug( + "Entering checkPassword($uid, *)", ["app" => $this->appName] + ); + + $passwordAlgorithm = $this->getPasswordAlgorithm(); + if ($passwordAlgorithm === null) { + return false; + } + + $user = $this->userRepository->findByUid($uid); + if (!($user instanceof User)) { + return false; + } + + $isCorrect = $passwordAlgorithm->checkPassword( + $password, $user->password + ); + + if ($isCorrect !== true) { + $this->logger->info( + "Invalid password attempt for user: $uid", + ["app" => $this->appName] + ); + return false; + } + + $this->logger->info( + "Successful password attempt for user: $uid", + ["app" => $this->appName] + ); + + return $uid; + } + + /** + * Get a password algorithm implementation instance. + * + * @return IPasswordAlgorithm The password algorithm instance or FALSE + * on failure. + */ + private function getPasswordAlgorithm() + { + $cryptoType = $this->properties[Opt::CRYPTO_CLASS]; + $passwordAlgorithm = new $cryptoType($this->localization); + + if ($passwordAlgorithm === null) { + $this->logger->error( + "Cannot get password algorithm instance: " . $cryptoType, + ["app" => $this->appName] + ); + } + + return $passwordAlgorithm; + } + + /** + * @inheritdoc + */ + public function getDisplayNames($search = "", $limit = null, $offset = null) + { + $this->logger->debug( + "Entering getDisplayNames($search, $limit, $offset)", + ["app" => $this->appName] + ); + + $users = $this->getUsers($search, $limit, $offset); + + $names = []; + foreach ($users as $user) { + $names[$user->uid] = $user->name; + } + + $this->logger->debug( + "Returning getDisplayNames($search, $limit, $offset): count(" + . count($users) . ")", ["app" => $this->appName] + ); + + return $names; + } + + /** + * @inheritdoc + */ + public function getUsers($search = "", $limit = null, $offset = null) + { + $this->logger->debug( + "Entering getUsers($search, $limit, $offset)", + ["app" => $this->appName] + ); + + $cacheKey = self::class . "users_" . $search . "_" . $limit . "_" + . $offset; + $users = $this->cache->get($cacheKey); + + if (!is_null($users)) { + $this->logger->debug( + "Returning from cache getUsers($search, $limit, $offset): count(" + . count($users) . ")", ["app" => $this->appName] + ); + return $users; + } + + $users = $this->userRepository->findAllBySearchTerm( + "%" . $search . "%", $limit, $offset + ); + + if ($users === false) { + return []; + } + + foreach ($users as $user) { + $this->cache->set("user_" . $user->uid, $user); + } + + $users = array_map( + function ($user) { + return $user->uid; + }, $users + ); + + $this->cache->set($cacheKey, $users); + $this->logger->debug( + "Returning getUsers($search, $limit, $offset): count(" . count( + $users + ) . ")", ["app" => $this->appName] + ); + + return $users; + } + + /** + * Set a user password. + * + * @param string $uid The user ID. + * @param string $password The password to set. + * + * @return bool TRUE if the password has been set, FALSE otherwise. + */ + public function setPassword($uid, $password) + { + $this->logger->debug( + "Entering setPassword($uid, *)", ["app" => "user_sql"] + ); + + $passwordAlgorithm = $this->getPasswordAlgorithm(); + if ($passwordAlgorithm === false) { + return false; + } + + $passwordHash = $passwordAlgorithm->getPasswordHash($password); + if ($passwordHash === false) { + return false; + } + + $user = $this->userRepository->findByUid($uid); + if (!($user instanceof User)) { + return false; + } + + $user->password = $passwordHash; + $result = $this->userRepository->save($user); + + if ($result === true) { + $this->logger->info( + "Password has been set successfully for user: $uid", + ["app" => $this->appName] + ); + return true; + } + + return false; + } + + /** + * @inheritdoc + */ + public function getHome($uid) + { + $this->logger->debug( + "Entering getHome($uid)", ["app" => $this->appName] + ); + + $home = false; + switch ($this->properties[Opt::HOME_MODE]) { + case App::HOME_STATIC: + $home = $this->properties[Opt::HOME_LOCATION]; + $home = str_replace("%u", $uid, $home); + break; + case App::HOME_QUERY: + $user = $this->getUser($uid); + if (!($user instanceof User)) { + return false; + } + $home = $user->home; + break; + } + + $this->logger->debug( + "Returning getHome($uid): " . $home, ["app" => $this->appName] + ); + + return $home; + } + + /** + * Can user change its avatar. + * + * @param string $uid The user ID. + * + * @return bool TRUE if the user can change its avatar, FALSE otherwise. + */ + public function canChangeAvatar($uid) + { + $this->logger->debug( + "Entering canChangeAvatar($uid)", ["app" => $this->appName] + ); + + $user = $this->userRepository->findByUid($uid); + if (!($user instanceof User)) { + return false; + } + + $avatar = $user->avatar; + $this->logger->debug( + "Returning canChangeAvatar($uid): " . ($avatar ? "true" + : "false"), ["app" => $this->appName] + ); + + return $avatar; + } + + /** + * Set a user display name. + * + * @param string $uid The user ID. + * @param string $displayName The display name to set. + * + * @return bool TRUE if the password has been set, FALSE otherwise. + */ + public function setDisplayName($uid, $displayName) + { + $this->logger->debug( + "Entering setDisplayName($uid, $displayName)", + ["app" => $this->appName] + ); + + $user = $this->userRepository->findByUid($uid); + if (!($user instanceof User)) { + return false; + } + + $user->name = $displayName; + $result = $this->userRepository->save($user); + + if ($result === true) { + $this->logger->info( + "Display name has been set successfully for user: $uid", + ["app" => $this->appName] + ); + return true; + } + + return false; + } + + /** + * @inheritdoc + */ + public function getSupportedActions() + { + $actions = parent::getSupportedActions(); + + $actions &= empty($this->properties[DB::USER_NAME_COLUMN]) + ? ~Backend::GET_DISPLAYNAME : ~0; + $actions &= empty($this->properties[Opt::HOME_MODE]) + ? ~Backend::GET_HOME : ~0; + $actions &= empty($this->properties[DB::USER_AVATAR_COLUMN]) + ? ~Backend::PROVIDE_AVATAR : ~0; + $actions &= (!empty($this->properties[DB::USER_NAME_COLUMN]) + && $this->properties[Opt::NAME_CHANGE]) ? ~0 + : ~Backend::SET_DISPLAYNAME; + $actions &= $this->properties[Opt::PASSWORD_CHANGE] ? ~0 + : ~Backend::SET_PASSWORD; + + return $actions; + } + + /** + * Check if this backend is correctly set and can be enabled. + * + * @return bool TRUE if all necessary options for this backend + * are configured, FALSE otherwise. + */ + public function isConfigured() + { + return !empty($this->properties[DB::DATABASE]) + && !empty($this->properties[DB::DRIVER]) + && !empty($this->properties[DB::HOSTNAME]) + && !empty($this->properties[DB::USERNAME]) + && !empty($this->properties[DB::USER_TABLE]) + && !empty($this->properties[DB::USER_UID_COLUMN]) + && !empty($this->properties[DB::USER_PASSWORD_COLUMN]) + && !empty($this->properties[Opt::CRYPTO_CLASS]); + } +} diff --git a/lib/Cache.php b/lib/Cache.php new file mode 100644 index 0000000..622deb7 --- /dev/null +++ b/lib/Cache.php @@ -0,0 +1,107 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL; + +use OC\Memcache\NullCache; +use OCA\UserSQL\Constant\App; +use OCA\UserSQL\Constant\Opt; +use OCP\ICache; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Used to store key-value pairs in the cache memory. + * If there's no distributed cache available NULL cache is used. + * + * @author Marcin Łojewski + */ +class Cache +{ + /** + * @var ICache The cache instance. + */ + private $cache; + + /** + * The default constructor. Initiates the cache memory. + * + * @param string $AppName The application name. + * @param IConfig $config The config instance. + * @param ILogger $logger The logger instance. + */ + public function __construct($AppName, IConfig $config, ILogger $logger) + { + $factory = \OC::$server->getMemCacheFactory(); + $useCache = $config->getAppValue( + $AppName, Opt::USE_CACHE, App::FALSE_VALUE + ); + + if ($useCache === App::FALSE_VALUE) { + $this->cache = new NullCache(); + } elseif ($factory->isAvailable()) { + $this->cache = $factory->createDistributed(); + $logger->debug("Distributed cache initiated.", ["app" => $AppName]); + } else { + $logger->warning( + "There's no distributed cache available, fallback to null cache.", + ["app" => $AppName] + ); + $this->cache = new NullCache(); + } + } + + /** + * Fetch a value from the cache memory. + * + * @param string $key The cache value key. + * + * @return mixed|NULL Cached value or NULL if there's no value stored. + */ + public function get($key) + { + return $this->cache->get($key); + } + + /** + * Store a value in the cache memory. + * + * @param string $key The cache value key. + * @param mixed $value The value to store. + * @param int $ttl (optional) TTL in seconds. Defaults to 1 hour. + * + * @return bool TRUE on success, FALSE otherwise. + */ + public function set($key, $value, $ttl = 3600) + { + return $this->cache->set($key, $value, $ttl); + } + + /** + * Clear the cache of all entries. + * + * @return bool TRUE on success, FALSE otherwise. + */ + public function clear() + { + return $this->cache->clear(); + } +} diff --git a/lib/Constant/App.php b/lib/Constant/App.php new file mode 100644 index 0000000..a2973ce --- /dev/null +++ b/lib/Constant/App.php @@ -0,0 +1,40 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Constant; + +/** + * The application constants. + * + * @author Marcin Łojewski + */ +final class App +{ + const FALSE_VALUE = "0"; + const TRUE_VALUE = "1"; + + const HOME_QUERY = "query"; + const HOME_STATIC = "static"; + + const EMAIL_FORCE_NC = "force_nc"; + const EMAIL_FORCE_SQL = "force_sql"; + const EMAIL_INITIAL = "initial"; +} diff --git a/lib/Constant/DB.php b/lib/Constant/DB.php new file mode 100644 index 0000000..51f50f0 --- /dev/null +++ b/lib/Constant/DB.php @@ -0,0 +1,54 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Constant; + +/** + * The database properties. + * + * @author Marcin Łojewski + */ +final class DB +{ + const DATABASE = "db.database"; + const DRIVER = "db.driver"; + const HOSTNAME = "db.hostname"; + const PASSWORD = "db.password"; + const USERNAME = "db.username"; + + const GROUP_TABLE = "db.table.group"; + const USER_GROUP_TABLE = "db.table.user_group"; + const USER_TABLE = "db.table.user"; + + const GROUP_ADMIN_COLUMN = "db.table.group.column.admin"; + const GROUP_GID_COLUMN = "db.table.group.column.gid"; + const GROUP_NAME_COLUMN = "db.table.group.column.name"; + + const USER_GROUP_GID_COLUMN = "db.table.user_group.column.gid"; + const USER_GROUP_UID_COLUMN = "db.table.user_group.column.uid"; + + const USER_AVATAR_COLUMN = "db.table.user.column.avatar"; + const USER_EMAIL_COLUMN = "db.table.user.column.email"; + const USER_HOME_COLUMN = "db.table.user.column.home"; + const USER_NAME_COLUMN = "db.table.user.column.name"; + const USER_PASSWORD_COLUMN = "db.table.user.column.password"; + const USER_UID_COLUMN = "db.table.user.column.uid"; +} diff --git a/lib/Constant/Opt.php b/lib/Constant/Opt.php new file mode 100644 index 0000000..56ce8b2 --- /dev/null +++ b/lib/Constant/Opt.php @@ -0,0 +1,38 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Constant; + +/** + * The option properties names. + * + * @author Marcin Łojewski + */ +final class Opt +{ + const CRYPTO_CLASS = "opt.crypto_class"; + const EMAIL_SYNC = "opt.email_sync"; + const HOME_LOCATION = "opt.home_location"; + const HOME_MODE = "opt.home_mode"; + const NAME_CHANGE = "opt.name_change"; + const PASSWORD_CHANGE = "opt.password_change"; + const USE_CACHE = "opt.use_cache"; +} diff --git a/lib/Constant/Query.php b/lib/Constant/Query.php new file mode 100644 index 0000000..f67183c --- /dev/null +++ b/lib/Constant/Query.php @@ -0,0 +1,47 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Constant; + +/** + * The database query constants. + * + * @author Marcin Łojewski + */ +final class Query +{ + const BELONGS_TO_ADMIN = "belongs_to_admin"; + const COUNT_GROUPS = "count_groups"; + const COUNT_USERS = "count_users"; + const FIND_GROUP = "find_group"; + const FIND_GROUP_USERS = "find_group_users"; + const FIND_GROUPS = "find_groups"; + const FIND_USER = "find_user"; + const FIND_USER_GROUPS = "find_user_groups"; + const FIND_USERS = "find_users"; + const SAVE_USER = "save_user"; + + const GID_PARAM = "gid"; + const NAME_PARAM = "name"; + const PASSWORD_PARAM = "password"; + const SEARCH_PARAM = "search"; + const UID_PARAM = "uid"; +} diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php new file mode 100644 index 0000000..7a95d6c --- /dev/null +++ b/lib/Controller/SettingsController.php @@ -0,0 +1,368 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Controller; + +use Doctrine\DBAL\DBALException; +use Exception; +use OC\DatabaseException; +use OC\DB\Connection; +use OC\DB\ConnectionFactory; +use OCA\UserSQL\Cache; +use OCA\UserSQL\Constant\App; +use OCA\UserSQL\Platform\PlatformFactory; +use OCA\UserSQL\Properties; +use OCP\AppFramework\Controller; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IRequest; + +/** + * The settings controller. + * + * @author Marcin Łojewski + */ +class SettingsController extends Controller +{ + /** + * @var ILogger The logger instance. + */ + private $logger; + /** + * @var IL10N The localization service. + */ + private $localization; + /** + * @var Properties The properties array. + */ + private $properties; + /** + * @var Cache The cache instance. + */ + private $cache; + + /** + * The default constructor. + * + * @param string $appName The application name. + * @param IRequest $request An instance of the request. + * @param ILogger $logger The logger instance. + * @param IL10N $localization The localization service. + * @param Properties $properties The properties array. + * @param Cache $cache The cache instance. + */ + public function __construct( + $appName, IRequest $request, ILogger $logger, IL10N $localization, + Properties $properties, Cache $cache + ) { + parent::__construct($appName, $request); + $this->appName = $appName; + $this->logger = $logger; + $this->localization = $localization; + $this->properties = $properties; + $this->cache = $cache; + } + + /** + * Verify the database connection parameters. + * + * @return array The request status. + */ + public function verifyDbConnection() + { + $this->logger->debug( + "Entering verifyDbConnection()", ["app" => $this->appName] + ); + + try { + $this->getConnection(); + + $this->logger->debug( + "Returning verifyDbConnection(): success", + ["app" => $this->appName] + ); + + return [ + "status" => "success", + "data" => [ + "message" => $this->localization->t( + "Successfully connected to the database." + ) + ] + ]; + } catch (Exception $exception) { + $this->logger->debug( + "Returning verifyDbConnection(): error", + ["app" => $this->appName] + ); + + return [ + "status" => "error", + "data" => [ + "message" => $this->localization->t( + "Error connecting to the database: " + ) . $exception->getMessage() + ] + ]; + } + } + + /** + * Get the database connection instance. + * + * @return Connection The database connection instance. + * @throws DBALException On database connection problems. + * @throws DatabaseException Whenever no database driver is specified. + */ + private function getConnection() + { + $dbDriver = $this->request->getParam("db-driver"); + $dbHostname = $this->request->getParam("db-hostname"); + $dbDatabase = $this->request->getParam("db-database"); + $dbUsername = $this->request->getParam("db-username"); + $dbPassword = $this->request->getParam("db-password"); + + if (empty($dbDriver)) { + throw new DatabaseException("No database driver specified."); + } + + $connectionFactory = new ConnectionFactory( + \OC::$server->getSystemConfig() + ); + + $parameters = [ + "host" => $dbHostname, + "password" => $dbPassword, + "user" => $dbUsername, + "dbname" => $dbDatabase, + "tablePrefix" => "" + ]; + + $connection = $connectionFactory->getConnection($dbDriver, $parameters); + $connection->executeQuery("SELECT 'user_sql'"); + + return $connection; + } + + /** + * Save application properties. + * + * @return array The request status. + */ + public function saveProperties() + { + $this->logger->debug( + "Entering saveProperties()", ["app" => $this->appName] + ); + + $properties = $this->properties->getArray(); + + try { + $this->getConnection(); + } catch (Exception $exception) { + $this->logger->debug( + "Returning saveProperties(): error", + ["app" => $this->appName] + ); + + return [ + "status" => "error", + "data" => [ + "message" => $this->localization->t( + "Error connecting to the database: " + ) . $exception->getMessage() + ] + ]; + } + + foreach ($properties as $key => $value) { + $reqValue = $this->request->getParam(str_replace(".", "-", $key)); + $appValue = $this->properties[$key]; + + if ((!is_bool($appValue) && isset($reqValue) + && $reqValue !== $appValue) + || (is_bool($appValue) && isset($reqValue) !== $appValue) + ) { + $value = isset($reqValue) ? $reqValue : App::FALSE_VALUE; + $this->properties[$key] = $value; + + $this->logger->info( + "Property '$key' has been set to: " . $value, + ["app" => $this->appName] + ); + } + } + + $this->logger->debug( + "Returning saveProperties(): success", ["app" => $this->appName] + ); + + return [ + "status" => "success", + "data" => [ + "message" => $this->localization->t( + "Properties has been saved." + ) + ] + ]; + } + + /** + * Clear the application cache memory. + * + * @return array The request status. + */ + public function clearCache() + { + $this->logger->debug( + "Entering clearCache()", ["app" => $this->appName] + ); + + $this->cache->clear(); + + $this->logger->info( + "Cache memory has been cleared.", ["app" => $this->appName] + ); + + return [ + "status" => "success", + "data" => [ + "message" => $this->localization->t( + "Cache memory has been cleared." + ) + ] + ]; + } + + /** + * Autocomplete for table select options. + * + * @return array The database table list. + */ + public function tableAutocomplete() + { + $this->logger->debug( + "Entering tableAutocomplete()", ["app" => $this->appName] + ); + + try { + $connection = $this->getConnection(); + $platform = PlatformFactory::getPlatform($connection); + $tables = $platform->getTables(); + + $this->logger->debug( + "Returning tableAutocomplete(): count(" . count($tables) . ")", + ["app" => $this->appName] + ); + + return $tables; + } catch (Exception $e) { + $this->logger->logException($e); + return []; + } + } + + /** + * Autocomplete for column select options - user table. + * + * @return array The database table's column list. + */ + public function userTableAutocomplete() + { + $this->logger->debug( + "Entering userTableAutocomplete()", ["app" => $this->appName] + ); + + $columns = $this->columnAutocomplete("db-table-user"); + + $this->logger->debug( + "Returning userTableAutocomplete(): count(" . count($columns) . ")", + ["app" => $this->appName] + ); + + return $columns; + } + + /** + * Autocomplete for column select options. + * + * @param string $table The table's form ID. + * + * @return array The table's column list. + */ + private function columnAutocomplete($table) + { + try { + $connection = $this->getConnection(); + $platform = PlatformFactory::getPlatform($connection); + $columns = $platform->getColumns( + $this->request->getParam($table) + ); + + return $columns; + } catch (Exception $e) { + $this->logger->logException($e); + return []; + } + } + + /** + * Autocomplete for column select options - user_group table. + * + * @return array The database table's column list. + */ + public function userGroupTableAutocomplete() + { + $this->logger->debug( + "Entering userGroupTableAutocomplete()", ["app" => $this->appName] + ); + + $columns = $this->columnAutocomplete("db-table-user_group"); + + $this->logger->debug( + "Returning userGroupTableAutocomplete(): count(" . count($columns) + . ")", ["app" => $this->appName] + ); + + return $columns; + } + + /** + * Autocomplete for column select options - group table. + * + * @return array The database table's column list. + */ + public function groupTableAutocomplete() + { + $this->logger->debug( + "Entering groupTableAutocomplete()", ["app" => $this->appName] + ); + + $columns = $this->columnAutocomplete("db-table-group"); + + $this->logger->debug( + "Returning groupTableAutocomplete(): count(" . count($columns) + . ")", ["app" => $this->appName] + ); + + return $columns; + } +} diff --git a/lib/Crypto/AbstractAlgorithm.php b/lib/Crypto/AbstractAlgorithm.php new file mode 100644 index 0000000..9556d78 --- /dev/null +++ b/lib/Crypto/AbstractAlgorithm.php @@ -0,0 +1,77 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * The abstract password algorithm class. + * Each algorithm should extend this class, as it provides very base + * functionality which seems to be necessary for every implementation. + * + * @author Marcin Łojewski + */ +abstract class AbstractAlgorithm implements IPasswordAlgorithm +{ + /** + * @var IL10N The localization service. + */ + private $localization; + + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + $this->localization = $localization; + } + + /** + * @inheritdoc + */ + public function getVisibleName() + { + return $this->localization->t($this->getAlgorithmName()); + } + + /** + * Get the algorithm name. + * + * @return string The algorithm name. + */ + protected abstract function getAlgorithmName(); + + /** + * @inheritdoc + */ + public function checkPassword($password, $dbHash) + { + return hash_equals($dbHash, $this->getPasswordHash($password)); + } + + /** + * @inheritdoc + */ + public abstract function getPasswordHash($password); +} diff --git a/lib/Crypto/AbstractCrypt.php b/lib/Crypto/AbstractCrypt.php new file mode 100644 index 0000000..c13e1b5 --- /dev/null +++ b/lib/Crypto/AbstractCrypt.php @@ -0,0 +1,63 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +/** + * Abstract Unix Crypt hashing implementation. + * The hashing algorithm depends on the chosen salt. + * + * @see crypt() + * @author Marcin Łojewski + */ +abstract class AbstractCrypt extends AbstractAlgorithm +{ + /** + * The chars used in the salt. + */ + const SALT_ALPHABET = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + /** + * @inheritdoc + */ + public function checkPassword($password, $dbHash) + { + return hash_equals($dbHash, crypt($password, $dbHash)); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return crypt($password, $this->getSalt()); + } + + /** + * Generate a salt string for the hashing algorithm. + * + * @return string The salt string. + */ + protected function getSalt() + { + return ""; + } +} diff --git a/lib/Crypto/Cleartext.php b/lib/Crypto/Cleartext.php new file mode 100644 index 0000000..e33d919 --- /dev/null +++ b/lib/Crypto/Cleartext.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Cleartext password implementation. + * + * @author Marcin Łojewski + */ +class Cleartext extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return $password; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Cleartext"; + } +} diff --git a/lib/Crypto/CourierMD5.php b/lib/Crypto/CourierMD5.php new file mode 100644 index 0000000..6e8e71f --- /dev/null +++ b/lib/Crypto/CourierMD5.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Courier MD5 hashing implementation. + * + * @author Marcin Łojewski + */ +class CourierMD5 extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return '{MD5}' . Utils::hexToBase64(md5($password)); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Courier base64-encoded MD5"; + } +} diff --git a/lib/Crypto/CourierMD5Raw.php b/lib/Crypto/CourierMD5Raw.php new file mode 100644 index 0000000..39fd3db --- /dev/null +++ b/lib/Crypto/CourierMD5Raw.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Courier MD5 RAW hashing implementation. + * + * @author Marcin Łojewski + */ +class CourierMD5Raw extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return '{MD5RAW}' . md5($password); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Courier hexadecimal MD5"; + } +} diff --git a/lib/Crypto/CourierSHA1.php b/lib/Crypto/CourierSHA1.php new file mode 100644 index 0000000..15d2ef3 --- /dev/null +++ b/lib/Crypto/CourierSHA1.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Courier SHA1 hashing implementation. + * + * @author Marcin Łojewski + */ +class CourierSHA1 extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return '{SHA}' . Utils::hexToBase64(sha1($password)); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Courier base64-encoded SHA1"; + } +} diff --git a/lib/Crypto/CourierSHA256.php b/lib/Crypto/CourierSHA256.php new file mode 100644 index 0000000..3bf0ed6 --- /dev/null +++ b/lib/Crypto/CourierSHA256.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Courier SHA256 hashing implementation. + * + * @author Marcin Łojewski + */ +class CourierSHA256 extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return '{SHA256}' . Utils::hexToBase64(hash('sha256', $password)); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Courier base64-encoded SHA256"; + } +} diff --git a/lib/Crypto/Crypt.php b/lib/Crypto/Crypt.php new file mode 100644 index 0000000..c52be8d --- /dev/null +++ b/lib/Crypto/Crypt.php @@ -0,0 +1,59 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Unix Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class Crypt extends AbstractCrypt +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return password_hash($password, PASSWORD_DEFAULT); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Unix (Crypt)"; + } +} diff --git a/lib/Crypto/CryptArgon2.php b/lib/Crypto/CryptArgon2.php new file mode 100644 index 0000000..a8c2d3e --- /dev/null +++ b/lib/Crypto/CryptArgon2.php @@ -0,0 +1,103 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Argon2 Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class CryptArgon2 extends AbstractAlgorithm +{ + /** + * @var int Maximum memory (in bytes) that may be used to compute. + */ + private $memoryCost; + /** + * @var int Maximum amount of time it may take to compute. + */ + private $timeCost; + /** + * @var int Number of threads to use for computing. + */ + private $threads; + + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + * @param int $memoryCost Maximum memory (in bytes) that may be used + * to compute. + * @param int $timeCost Maximum amount of time it may take to compute. + * @param int $threads Number of threads to use for computing. + */ + public function __construct( + IL10N $localization, + $memoryCost = PASSWORD_ARGON2_DEFAULT_MEMORY_COST, + $timeCost = PASSWORD_ARGON2_DEFAULT_TIME_COST, + $threads = PASSWORD_ARGON2_DEFAULT_THREADS + ) { + if (version_compare(PHP_VERSION, "7.2.0") === -1) { + throw new \RuntimeException( + "PASSWORD_ARGON2I requires PHP 7.2.0 or above." + ); + } + + parent::__construct($localization); + $this->memoryCost = $memoryCost; + $this->timeCost = $timeCost; + $this->threads = $threads; + } + + /** + * @inheritdoc + */ + public function checkPassword($password, $dbHash) + { + return password_verify($password, $dbHash); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return password_hash( + $password, PASSWORD_ARGON2I, [ + "memory_cost" => $this->memoryCost, + "time_cost" => $this->timeCost, + "threads" => $this->threads + ] + ); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Argon2 (Crypt)"; + } +} diff --git a/lib/Crypto/CryptBlowfish.php b/lib/Crypto/CryptBlowfish.php new file mode 100644 index 0000000..8e4a35e --- /dev/null +++ b/lib/Crypto/CryptBlowfish.php @@ -0,0 +1,79 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Blowfish Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class CryptBlowfish extends AbstractAlgorithm +{ + /** + * @var int Denotes the algorithmic cost that should be used. + */ + private $cost; + + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + * @param int $cost Denotes the algorithmic cost that should + * be used. + */ + public function __construct(IL10N $localization, $cost = 10) + { + parent::__construct($localization); + $this->cost = $cost; + } + + /** + * @inheritdoc + */ + public function checkPassword($password, $dbHash) + { + return password_verify($password, $dbHash); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return password_hash( + $password, PASSWORD_BCRYPT, ["cost" => $this->cost] + ); + } + + /** + * Get the algorithm name. + * + * @return string The algorithm name. + */ + protected function getAlgorithmName() + { + return "Blowfish (Crypt)"; + } +} diff --git a/lib/Crypto/CryptExtendedDES.php b/lib/Crypto/CryptExtendedDES.php new file mode 100644 index 0000000..b09baab --- /dev/null +++ b/lib/Crypto/CryptExtendedDES.php @@ -0,0 +1,92 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Extended DES Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class CryptExtendedDES extends AbstractCrypt +{ + /** + * @var int The number of iterations. + */ + private $iterationCount; + + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + * @param int $iterationCount The number of iterations. + */ + public function __construct(IL10N $localization, $iterationCount = 1000) + { + parent::__construct($localization); + $this->iterationCount = $iterationCount; + } + + /** + * @inheritdoc + */ + protected function getSalt() + { + return self::encodeIterationCount($this->iterationCount) + . Utils::randomString(4, self::SALT_ALPHABET); + } + + /** + * Get the number of iterations as describe below. + * The 4 bytes of iteration count are encoded as printable characters, + * 6 bits per character, least significant character first. + * The values 0 to 63 are encoded as "./0-9A-Za-z". + * + * @param int $number The number of iterations. + * + * @return string + */ + private static function encodeIterationCount($number) + { + $alphabet = str_split(self::SALT_ALPHABET); + $chars = array(); + $base = sizeof($alphabet); + + while ($number) { + $rem = $number % $base; + $number = (int)($number / $base); + $arr[] = $alphabet[$rem]; + } + + return str_pad(implode($chars), 4, ".", STR_PAD_RIGHT); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Extended DES (Crypt)"; + } +} diff --git a/lib/Crypto/CryptMD5.php b/lib/Crypto/CryptMD5.php new file mode 100644 index 0000000..6ca2e3b --- /dev/null +++ b/lib/Crypto/CryptMD5.php @@ -0,0 +1,59 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * MD5 Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class CryptMD5 extends AbstractCrypt +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + protected function getSalt() + { + return "$1$" . Utils::randomString(8, self::SALT_ALPHABET) . "$"; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "MD5 (Crypt)"; + } +} diff --git a/lib/Crypto/CryptSHA256.php b/lib/Crypto/CryptSHA256.php new file mode 100644 index 0000000..fad91b3 --- /dev/null +++ b/lib/Crypto/CryptSHA256.php @@ -0,0 +1,69 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * SHA256 Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class CryptSHA256 extends AbstractCrypt +{ + /** + * @var int The number of rounds. + */ + private $rounds; + + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + * @param int $rounds The number of rounds. + * This value must be between 1000 and 999999999. + */ + public function __construct(IL10N $localization, $rounds = 5000) + { + parent::__construct($localization); + $this->rounds = $rounds; + } + + /** + * @inheritdoc + */ + protected function getSalt() + { + return "$5\$rounds=" . $this->rounds . "$" . Utils::randomString( + 16, self::SALT_ALPHABET + ) . "$"; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "SHA256 (Crypt)"; + } +} diff --git a/lib/Crypto/CryptSHA512.php b/lib/Crypto/CryptSHA512.php new file mode 100644 index 0000000..11f3b8f --- /dev/null +++ b/lib/Crypto/CryptSHA512.php @@ -0,0 +1,69 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * SHA512 Crypt hashing implementation. + * + * @see crypt() + * @author Marcin Łojewski + */ +class CryptSHA512 extends AbstractCrypt +{ + /** + * @var int The number of rounds. + */ + private $rounds; + + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + * @param int $rounds The number of rounds. + * This value must be between 1000 and 999999999. + */ + public function __construct(IL10N $localization, $rounds = 5000) + { + parent::__construct($localization); + $this->rounds = $rounds; + } + + /** + * @inheritdoc + */ + protected function getSalt() + { + return "$6\$rounds=" . $this->rounds . "$" . Utils::randomString( + 16, self::SALT_ALPHABET + ) . "$"; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "SHA512 (Crypt)"; + } +} diff --git a/lib/Crypto/CryptStandardDES.php b/lib/Crypto/CryptStandardDES.php new file mode 100644 index 0000000..7d8fa7d --- /dev/null +++ b/lib/Crypto/CryptStandardDES.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Standard DES Crypt hashing implementation. + * + * @author Marcin Łojewski + */ +class CryptStandardDES extends AbstractCrypt +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + protected function getSalt() + { + return Utils::randomString(2, self::SALT_ALPHABET); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Standard DES (Crypt)"; + } +} diff --git a/lib/Crypto/IPasswordAlgorithm.php b/lib/Crypto/IPasswordAlgorithm.php new file mode 100644 index 0000000..47ba961 --- /dev/null +++ b/lib/Crypto/IPasswordAlgorithm.php @@ -0,0 +1,59 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +/** + * Interface which defines all function required by a hash algorithm. + * Please note that this interface must be implemented by every hash function supported in this app. + * + * @author Marcin Łojewski + */ +interface IPasswordAlgorithm +{ + /** + * Get the hash algorithm name. + * This name is visible in the admin panel. + * + * @return string + */ + public function getVisibleName(); + + /** + * Hash given password. + * This value is stored in the database, when the password is changed. + * + * @param String $password The new password. + * + * @return boolean True if the password was hashed successfully, false otherwise. + */ + public function getPasswordHash($password); + + /** + * Check password given by the user against hash stored in the database. + * + * @param String $password Password given by the user. + * @param String $dbHash Password hash stored in the database. + * + * @return boolean True if the password is correct, false otherwise. + */ + public function checkPassword($password, $dbHash); +} diff --git a/lib/Crypto/Joomla.php b/lib/Crypto/Joomla.php new file mode 100644 index 0000000..e5dd2ca --- /dev/null +++ b/lib/Crypto/Joomla.php @@ -0,0 +1,82 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * Joomla hashing implementation. + * + * @author Marcin Łojewski + */ +class Joomla extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + $salt = Utils::randomString( + 32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + ); + + return md5($password . $salt) . ":" . $salt; + } + + /** + * @inheritdoc + */ + public function checkPassword($password, $dbHash) + { + return hash_equals($dbHash, self::generateHash($password, $dbHash)); + } + + private static function generateHash($password, $dbHash) + { + $split_salt = preg_split("/:/", $dbHash); + $salt = false; + if (isset($split_salt[1])) { + $salt = $split_salt[1]; + } + $pwHash = ($salt) ? md5($password . $salt) : md5($password); + $pwHash .= ":" . $salt; + return $pwHash; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Joomla MD5 Encryption"; + } +} diff --git a/lib/Crypto/MD5.php b/lib/Crypto/MD5.php new file mode 100644 index 0000000..a4ba435 --- /dev/null +++ b/lib/Crypto/MD5.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * MD5 hashing implementation. + * + * @author Marcin Łojewski + */ +class MD5 extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return md5($password); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "MD5"; + } +} diff --git a/lib/Crypto/SHA1.php b/lib/Crypto/SHA1.php new file mode 100644 index 0000000..a534212 --- /dev/null +++ b/lib/Crypto/SHA1.php @@ -0,0 +1,58 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * SHA1 hashing implementation. + * + * @author Marcin Łojewski + */ +class SHA1 extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return sha1($password); + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "SHA1"; + } +} diff --git a/lib/Crypto/SSHA.php b/lib/Crypto/SSHA.php new file mode 100644 index 0000000..f0f46d9 --- /dev/null +++ b/lib/Crypto/SSHA.php @@ -0,0 +1,105 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * SSHA* hashing implementation. + * + * @author Marcin Łojewski + */ +abstract class SSHA extends AbstractAlgorithm +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function checkPassword($password, $dbHash) + { + $saltedPassword = base64_decode( + preg_replace("/" . $this->getPrefix() . "/i", "", $dbHash) + ); + $salt = substr($saltedPassword, -(strlen($saltedPassword) - $this->getHashLength())); + $hash = self::ssha($password, $salt); + + return hash_equals($dbHash, $hash); + } + + /** + * Get hash prefix eg. {SSHA256}. + * + * @return string The hash prefix. + */ + public abstract function getPrefix(); + + /** + * Encrypt using SSHA* algorithm. + * + * @param string $password The password. + * @param string $salt The salt to use. + * + * @return string The hashed password, prefixed by {SSHA*}. + */ + private function ssha($password, $salt) + { + return $this->getPrefix() . base64_encode( + hash($this->getAlgorithm(), $password . $salt, true) . $salt + ); + } + + /** + * Get algorithm used by the hash() function. + * + * @see hash() + * @return string + */ + public abstract function getAlgorithm(); + + /** + * Get hash length. + * + * @return int The hash length. + */ + public abstract function getHashLength(); + + /** + * @inheritdoc + */ + public function getPasswordHash($password) + { + return self::ssha( + $password, Utils::randomString( + 32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + ) + ); + } +} diff --git a/lib/Crypto/SSHA256.php b/lib/Crypto/SSHA256.php new file mode 100644 index 0000000..a01cdf9 --- /dev/null +++ b/lib/Crypto/SSHA256.php @@ -0,0 +1,74 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * SSHA256 hashing implementation. + * + * @author Marcin Łojewski + */ +class SSHA256 extends SSHA +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPrefix() + { + return "{SSHA256}"; + } + + /** + * @inheritdoc + */ + public function getAlgorithm() + { + return "sha256"; + } + + /** + * @inheritdoc + */ + public function getHashLength() + { + return 32; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "SSHA256"; + } +} diff --git a/lib/Crypto/SSHA512.php b/lib/Crypto/SSHA512.php new file mode 100644 index 0000000..78e66a1 --- /dev/null +++ b/lib/Crypto/SSHA512.php @@ -0,0 +1,74 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +/** + * SSHA512 hashing implementation. + * + * @author Marcin Łojewski + */ +class SSHA512 extends SSHA +{ + /** + * The class constructor. + * + * @param IL10N $localization The localization service. + */ + public function __construct(IL10N $localization) + { + parent::__construct($localization); + } + + /** + * @inheritdoc + */ + public function getPrefix() + { + return "{SSHA512}"; + } + + /** + * @inheritdoc + */ + public function getAlgorithm() + { + return "sha512"; + } + + /** + * @inheritdoc + */ + public function getHashLength() + { + return 64; + } + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "SSHA512"; + } +} diff --git a/lib/Crypto/Utils.php b/lib/Crypto/Utils.php new file mode 100644 index 0000000..4bd0651 --- /dev/null +++ b/lib/Crypto/Utils.php @@ -0,0 +1,63 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +/** + * Cryptographic utilities. + * + * @author Marcin Łojewski + */ +final class Utils +{ + /** + * Convert hexadecimal message to its base64 form. + * + * @param $hex string The hexadecimal encoded message. + * + * @return string The same message encoded in base64. + */ + public static function hexToBase64($hex) + { + $hexChr = ""; + foreach (str_split($hex, 2) as $hexPair) { + $hexChr .= chr(hexdec($hexPair)); + } + return base64_encode($hexChr); + } + + /** + * Generate random string from given alphabet. + * + * @param $length int The output string length. + * @param $alphabet string The output string alphabet. + * + * @return string Random string from given alphabet. + */ + public static function randomString($length, $alphabet) + { + $string = ""; + for ($idx = 0; $idx != $length; ++$idx) { + $string .= $alphabet[mt_rand(0, strlen($alphabet) - 1)]; + } + return $string; + } +} diff --git a/lib/Model/Group.php b/lib/Model/Group.php new file mode 100644 index 0000000..a839754 --- /dev/null +++ b/lib/Model/Group.php @@ -0,0 +1,43 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Model; + +/** + * The group entity. + * + * @author Marcin Łojewski + */ +class Group +{ + /** + * @var string The GID (group name). + */ + public $gid; + /** + * @var string The group's display name. + */ + public $name; + /** + * @var bool Whether it is an admin group. + */ + public $admin; +} diff --git a/lib/Model/User.php b/lib/Model/User.php new file mode 100644 index 0000000..65aed5b --- /dev/null +++ b/lib/Model/User.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Model; + +/** + * The user entity. + * + * @author Marcin Łojewski + */ +class User +{ + /** + * @var string The UID (username). + */ + public $uid; + /** + * @var string The user's email address. + */ + public $email; + /** + * @var string The user's display name. + */ + public $name; + /** + * @var string The user's password (hash). + */ + public $password; + /** + * @var string The user's home location. + */ + public $home; + /** + * @var bool Can user change its avatar. + */ + public $avatar; +} diff --git a/lib/PasswordHash.php b/lib/PasswordHash.php deleted file mode 100644 index e363656..0000000 --- a/lib/PasswordHash.php +++ /dev/null @@ -1,253 +0,0 @@ - in 2004-2006 and placed in -# the public domain. Revised in subsequent years, still public domain. -# -# There's absolutely no warranty. -# -# The homepage URL for this framework is: -# -# http://www.openwall.com/phpass/ -# -# Please be sure to update the Version line if you edit this file in any way. -# It is suggested that you leave the main version number intact, but indicate -# your project name (after the slash) and add your own revision information. -# -# Please do not change the "private" password hashing method implemented in -# here, thereby making your hashes incompatible. However, if you must, please -# change the hash type identifier (the "$P$") to something different. -# -# Obviously, since this code is in the public domain, the above are not -# requirements (there can be none), but merely suggestions. -# -class PasswordHash { - var $itoa64; - var $iteration_count_log2; - var $portable_hashes; - var $random_state; - - function __construct($iteration_count_log2, $portable_hashes) - { - $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - - if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) - $iteration_count_log2 = 8; - $this->iteration_count_log2 = $iteration_count_log2; - - $this->portable_hashes = $portable_hashes; - - $this->random_state = microtime(); - if (function_exists('getmypid')) - $this->random_state .= getmypid(); - } - - function get_random_bytes($count) - { - $output = ''; - if (is_readable('/dev/urandom') && - ($fh = @fopen('/dev/urandom', 'rb'))) { - $output = fread($fh, $count); - fclose($fh); - } - - if (strlen($output) < $count) { - $output = ''; - for ($i = 0; $i < $count; $i += 16) { - $this->random_state = - md5(microtime() . $this->random_state); - $output .= - pack('H*', md5($this->random_state)); - } - $output = substr($output, 0, $count); - } - - return $output; - } - - function encode64($input, $count) - { - $output = ''; - $i = 0; - do { - $value = ord($input[$i++]); - $output .= $this->itoa64[$value & 0x3f]; - if ($i < $count) - $value |= ord($input[$i]) << 8; - $output .= $this->itoa64[($value >> 6) & 0x3f]; - if ($i++ >= $count) - break; - if ($i < $count) - $value |= ord($input[$i]) << 16; - $output .= $this->itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) - break; - $output .= $this->itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); - - return $output; - } - - function gensalt_private($input) - { - $output = '$P$'; - $output .= $this->itoa64[min($this->iteration_count_log2 + - ((PHP_VERSION >= '5') ? 5 : 3), 30)]; - $output .= $this->encode64($input, 6); - - return $output; - } - - function crypt_private($password, $setting) - { - $output = '*0'; - if (substr($setting, 0, 2) === $output) - $output = '*1'; - - $id = substr($setting, 0, 3); - # We use "$P$", phpBB3 uses "$H$" for the same thing - if ($id !== '$P$' && $id !== '$H$') - return $output; - - $count_log2 = strpos($this->itoa64, $setting[3]); - if ($count_log2 < 7 || $count_log2 > 30) - return $output; - - $count = 1 << $count_log2; - - $salt = substr($setting, 4, 8); - if (strlen($salt) !== 8) - return $output; - - # We're kind of forced to use MD5 here since it's the only - # cryptographic primitive available in all versions of PHP - # currently in use. To implement our own low-level crypto - # in PHP would result in much worse performance and - # consequently in lower iteration counts and hashes that are - # quicker to crack (by non-PHP code). - if (PHP_VERSION >= '5') { - $hash = md5($salt . $password, TRUE); - do { - $hash = md5($hash . $password, TRUE); - } while (--$count); - } else { - $hash = pack('H*', md5($salt . $password)); - do { - $hash = pack('H*', md5($hash . $password)); - } while (--$count); - } - - $output = substr($setting, 0, 12); - $output .= $this->encode64($hash, 16); - - return $output; - } - - function gensalt_extended($input) - { - $count_log2 = min($this->iteration_count_log2 + 8, 24); - # This should be odd to not reveal weak DES keys, and the - # maximum valid value is (2**24 - 1) which is odd anyway. - $count = (1 << $count_log2) - 1; - - $output = '_'; - $output .= $this->itoa64[$count & 0x3f]; - $output .= $this->itoa64[($count >> 6) & 0x3f]; - $output .= $this->itoa64[($count >> 12) & 0x3f]; - $output .= $this->itoa64[($count >> 18) & 0x3f]; - - $output .= $this->encode64($input, 3); - - return $output; - } - - function gensalt_blowfish($input) - { - # This one needs to use a different order of characters and a - # different encoding scheme from the one in encode64() above. - # We care because the last character in our encoded string will - # only represent 2 bits. While two known implementations of - # bcrypt will happily accept and correct a salt string which - # has the 4 unused bits set to non-zero, we do not want to take - # chances and we also do not want to waste an additional byte - # of entropy. - $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - $output = '$2a$'; - $output .= chr(ord('0') + $this->iteration_count_log2 / 10); - $output .= chr(ord('0') + $this->iteration_count_log2 % 10); - $output .= '$'; - - $i = 0; - do { - $c1 = ord($input[$i++]); - $output .= $itoa64[$c1 >> 2]; - $c1 = ($c1 & 0x03) << 4; - if ($i >= 16) { - $output .= $itoa64[$c1]; - break; - } - - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 4; - $output .= $itoa64[$c1]; - $c1 = ($c2 & 0x0f) << 2; - - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 6; - $output .= $itoa64[$c1]; - $output .= $itoa64[$c2 & 0x3f]; - } while (1); - - return $output; - } - - function HashPassword($password) - { - $random = ''; - - if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) { - $random = $this->get_random_bytes(16); - $hash = - crypt($password, $this->gensalt_blowfish($random)); - if (strlen($hash) === 60) - return $hash; - } - - if (CRYPT_EXT_DES === 1 && !$this->portable_hashes) { - if (strlen($random) < 3) - $random = $this->get_random_bytes(3); - $hash = - crypt($password, $this->gensalt_extended($random)); - if (strlen($hash) === 20) - return $hash; - } - - if (strlen($random) < 6) - $random = $this->get_random_bytes(6); - $hash = - $this->crypt_private($password, - $this->gensalt_private($random)); - if (strlen($hash) === 34) - return $hash; - - # Returning '*' on error is safe here, but would _not_ be safe - # in a crypt(3)-like function used _both_ for generating new - # hashes and for validating passwords against existing hashes. - return '*'; - } - - function CheckPassword($password, $stored_hash) - { - $hash = $this->crypt_private($password, $stored_hash); - if ($hash[0] === '*') - $hash = crypt($password, $stored_hash); - - return $hash === $stored_hash; - } -} - -?> diff --git a/lib/Platform/AbstractPlatform.php b/lib/Platform/AbstractPlatform.php new file mode 100644 index 0000000..63156ff --- /dev/null +++ b/lib/Platform/AbstractPlatform.php @@ -0,0 +1,135 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Platform; + +use Doctrine\DBAL\DBALException; +use OC\DB\Connection; + +/** + * Database platform tools. + * + * @author Marcin Łojewski + */ +abstract class AbstractPlatform +{ + /** + * @var Connection The database connection. + */ + protected $connection; + + /** + * The class constructor. + * + * @param Connection $connection The database connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Get all the tables defined in the database. + * + * @param bool $schemaPrefix Show schema name in the results. + * + * @return array Array with table names. + * @throws DBALException On a database exception. + */ + public function getTables($schemaPrefix = false) + { + $platform = $this->connection->getDatabasePlatform(); + + $queryTables = $platform->getListTablesSQL(); + $queryViews = $platform->getListViewsSQL( + $this->connection->getDatabase() + ); + + $tables = array(); + + $result = $this->connection->executeQuery($queryTables); + while ($row = $result->fetch()) { + $name = $this->getTableName($row, $schemaPrefix); + $tables[] = $name; + } + + $result = $this->connection->executeQuery($queryViews); + while ($row = $result->fetch()) { + $name = $this->getViewName($row, $schemaPrefix); + $tables[] = $name; + } + + return $tables; + } + + /** + * Get a table name from a query result row. + * + * @param array $row The query result row. + * @param string $schema Put schema name in the result. + * + * @return string The table name retrieved from the row. + */ + protected abstract function getTableName($row, $schema); + + /** + * Get a view name from a query result row. + * + * @param array $row The query result row. + * @param string $schema Put schema name in the result. + * + * @return string The view name retrieved from the row. + */ + protected abstract function getViewName($row, $schema); + + /** + * Get all the columns defined in the table. + * + * @param string $table The table name. + * + * @return array Array with column names. + * @throws DBALException On a database exception. + */ + public function getColumns($table) + { + $platform = $this->connection->getDatabasePlatform(); + $query = $platform->getListTableColumnsSQL($table); + $result = $this->connection->executeQuery($query); + + $columns = array(); + + while ($row = $result->fetch()) { + $name = $this->getColumnName($row); + $columns[] = $name; + } + + return $columns; + } + + /** + * Get a column name from a query result row. + * + * @param array $row The query result row. + * + * @return string The column name retrieved from the row. + */ + protected abstract function getColumnName($row); +} diff --git a/lib/Platform/MySQLPlatform.php b/lib/Platform/MySQLPlatform.php new file mode 100644 index 0000000..4e47c09 --- /dev/null +++ b/lib/Platform/MySQLPlatform.php @@ -0,0 +1,66 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Platform; + +use OC\DB\Connection; + +/** + * MySQL database platform. + * + * @author Marcin Łojewski + */ +class MySQLPlatform extends AbstractPlatform +{ + /** + * The class constructor. + * + * @param Connection $connection The database connection. + */ + public function __construct(Connection $connection) + { + parent::__construct($connection); + } + + /** + * @inheritdoc + */ + protected function getViewName($row, $schema) + { + return $row["TABLE_NAME"]; + } + + /** + * @inheritdoc + */ + protected function getTableName($row, $schema) + { + return $row["Tables_in_" . $this->connection->getDatabase()]; + } + + /** + * @inheritdoc + */ + protected function getColumnName($row) + { + return $row["Field"]; + } +} diff --git a/lib/Platform/PlatformFactory.php b/lib/Platform/PlatformFactory.php new file mode 100644 index 0000000..328e591 --- /dev/null +++ b/lib/Platform/PlatformFactory.php @@ -0,0 +1,54 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Platform; + +use OC\DB\Connection; + +/** + * Factory for the database platform class instance. + * + * @author Marcin Łojewski + */ +class PlatformFactory +{ + /** + * Get the database platform. + * + * @param Connection $connection The database connection. + * + * @return AbstractPlatform The database platform. + */ + public static function getPlatform(Connection $connection) + { + switch ($connection->getDriver()->getName()) { + case "pdo_mysql": + return new MySQLPlatform($connection); + case "pdo_pgsql": + return new PostgreSQLPlatform($connection); + default: + throw new \InvalidArgumentException( + "Unknown database driver: " . $connection->getDriver()->getName( + ) + ); + } + } +} diff --git a/lib/Platform/PostgreSQLPlatform.php b/lib/Platform/PostgreSQLPlatform.php new file mode 100644 index 0000000..d9611c0 --- /dev/null +++ b/lib/Platform/PostgreSQLPlatform.php @@ -0,0 +1,68 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Platform; + +use OC\DB\Connection; + +/** + * PostgreSQL database platform. + * + * @author Marcin Łojewski + */ +class PostgreSQLPlatform extends AbstractPlatform +{ + /** + * The class constructor. + * + * @param Connection $connection The database connection. + */ + public function __construct(Connection $connection) + { + parent::__construct($connection); + } + + /** + * @inheritdoc + */ + protected function getViewName($row, $schema) + { + $schema ? ($row["schemaname"] . "." . $row["viewname"]) + : $row["viewname"]; + } + + /** + * @inheritdoc + */ + protected function getTableName($row, $schema) + { + $schema ? ($row["schema_name"] . "." . $row["table_name"]) + : $row["table_name"]; + } + + /** + * @inheritdoc + */ + protected function getColumnName($row) + { + return $row["field"]; + } +} diff --git a/lib/Properties.php b/lib/Properties.php new file mode 100644 index 0000000..b030b82 --- /dev/null +++ b/lib/Properties.php @@ -0,0 +1,211 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL; + +use OCA\UserSQL\Constant\App; +use OCA\UserSQL\Constant\DB; +use OCA\UserSQL\Constant\Opt; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Store and retrieve application properties. + * + * @author Marcin Łojewski + */ +class Properties implements \ArrayAccess +{ + /** + * @var string The cache key name. + */ + const CACHE_KEY = "Properties_data"; + + /** + * @var string The application name. + */ + private $appName; + /** + * @var IConfig The config instance. + */ + private $config; + /** + * @var ILogger The logger instance. + */ + private $logger; + /** + * @var Cache The cache instance. + */ + private $cache; + /** + * @var array The properties array. + */ + private $data; + + /** + * The default constructor. + * + * @param string $AppName The application name. + * @param IConfig $config The config instance. + * @param ILogger $logger The logger instance. + * @param Cache $cache The cache instance. + */ + public function __construct( + $AppName, IConfig $config, ILogger $logger, Cache $cache + ) { + $this->appName = $AppName; + $this->config = $config; + $this->logger = $logger; + $this->cache = $cache; + + $this->loadProperties(); + } + + /** + * Load the application properties. + * + * First the values are fetched from the cache memory. + * If these are not available, the database values are fetched. + */ + private function loadProperties() + { + $this->data = $this->cache->get(self::CACHE_KEY); + + if (!is_null($this->data)) { + return; + } + + $params = $this->getParameterArray(); + $this->data = []; + + foreach ($params as $param) { + $value = $this->config->getAppValue($this->appName, $param, null); + + if ($value === App::FALSE_VALUE) { + $value = false; + } elseif ($value === App::TRUE_VALUE) { + $value = true; + } + + $this->data[$param] = $value; + } + + $this->store(); + + $this->logger->debug( + "The application properties has been loaded.", + ["app" => $this->appName] + ); + } + + /** + * Return an array with all supported parameters. + * + * @return array Array containing strings of the parameters. + */ + private function getParameterArray() + { + $params = []; + + foreach ([DB::class, Opt::class] as $class) { + try { + $reflection = new \ReflectionClass($class); + $params = array_merge( + $params, array_values($reflection->getConstants()) + ); + } catch (\ReflectionException $exception) { + $this->logger->logException( + $exception, ["app" => $this->appName] + ); + } + } + + return $params; + } + + /** + * Store properties in the cache memory. + */ + private function store() + { + $this->cache->set(self::CACHE_KEY, $this->data); + } + + /** + * Get properties array. + * + * @return array The properties array. + */ + public function getArray() + { + return $this->data; + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + if (isset($this->data[$offset])) { + return $this->data[$offset]; + } else { + return null; + } + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->config->setAppValue($this->appName, $offset, $value); + + if ($value === App::FALSE_VALUE) { + $value = false; + } elseif ($value === App::TRUE_VALUE) { + $value = true; + } + + $this->data[$offset] = $value; + + if ($offset === Opt::USE_CACHE && $value === false) { + $this->cache->clear(); + } else { + $this->store(); + } + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } +} diff --git a/lib/Query/DataQuery.php b/lib/Query/DataQuery.php new file mode 100644 index 0000000..8f7f990 --- /dev/null +++ b/lib/Query/DataQuery.php @@ -0,0 +1,267 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Query; + +use Doctrine\DBAL\Driver\Statement; +use OC\DB\Connection; +use OC\DB\ConnectionFactory; +use OCA\UserSQL\Constant\DB; +use OCA\UserSQL\Constant\Query; +use OCA\UserSQL\Properties; +use OCP\ILogger; + +/** + * Used to query a database. + * + * @author Marcin Łojewski + */ +class DataQuery +{ + /** + * @var string The application name. + */ + private $appName; + /** + * @var ILogger The logger instance. + */ + private $logger; + /** + * @var Properties The properties array. + */ + private $properties; + /** + * @var QueryProvider The query provider. + */ + private $queryProvider; + /** + * @var Connection The database connection. + */ + private $connection; + + /** + * The class constructor. + * + * @param string $AppName The application name. + * @param ILogger $logger The logger instance. + * @param Properties $properties The properties array. + * @param QueryProvider $queryProvider The query provider. + */ + public function __construct( + $AppName, ILogger $logger, Properties $properties, + QueryProvider $queryProvider + ) { + $this->appName = $AppName; + $this->logger = $logger; + $this->properties = $properties; + $this->queryProvider = $queryProvider; + $this->connection = false; + } + + /** + * Execute an update query. + * + * @param string $queryName The query name. + * @param array $params The query parameters. + * + * @see Query + * @return bool TRUE on success, FALSE otherwise. + */ + public function update($queryName, $params = []) + { + return $this->execQuery($queryName, $params) !== false; + } + + /** + * Run a given query and return the result. + * + * @param string $queryName The query to execute. + * @param array $params The query parameters to bind. + * @param int $limit Results limit. Defaults to -1 (no limit). + * @param int $offset Results offset. Defaults to 0. + * + * @return Statement|bool Result of query or FALSE on failure. + */ + private function execQuery( + $queryName, $params = [], $limit = -1, $offset = 0 + ) { + if ($this->connection === false) { + $this->connectToDatabase(); + } + + $query = $this->queryProvider[$queryName]; + $result = $this->connection->prepare($query, $limit, $offset); + + foreach ($params as $param => $value) { + $result->bindValue(":" . $param, $value); + } + + $this->logger->debug( + "Executing query:" . $query . ", " . implode(",", $params), + ["app" => $this->appName] + ); + + if ($result->execute() !== true) { + $error = $result->errorInfo(); + $this->logger->error( + "Could not execute the query: " . implode(", ", $error), + ["app" => $this->appName] + ); + return false; + } + + return $result; + } + + /** + * Connect to the database using Nextcloud's DBAL. + */ + private function connectToDatabase() + { + $connectionFactory = new ConnectionFactory( + \OC::$server->getSystemConfig() + ); + + $parameters = array( + "host" => $this->properties[DB::HOSTNAME], + "password" => $this->properties[DB::PASSWORD], + "user" => $this->properties[DB::USERNAME], + "dbname" => $this->properties[DB::DATABASE], + "tablePrefix" => "" + ); + + $this->connection = $connectionFactory->getConnection( + $this->properties[DB::DRIVER], $parameters + ); + + $this->logger->debug( + "Database connection established.", ["app" => $this->appName] + ); + } + + /** + * Fetch a value from the first row and the first column which + * the given query returns. Empty result set is consider to be a failure. + * + * @param string $queryName The query to execute. + * @param array $params The query parameters to bind. + * @param bool $failure Value returned on database query failure. + * Defaults to FALSE. + * + * @return array|bool Queried value or $failure value on failure. + */ + public function queryValue($queryName, $params = [], $failure = false) + { + $result = $this->execQuery($queryName, $params); + if ($result === false) { + return false; + } + + $row = $result->fetch(\PDO::FETCH_COLUMN); + if ($row === false) { + return $failure; + } + + return $row; + } + + /** + * Fetch values from the first column which the given query returns. + * + * @param string $queryName The query to execute. + * @param array $params The query parameters to bind. + * @param int $limit Results limit. Defaults to -1 (no limit). + * @param int $offset Results offset. Defaults to 0. + * + * @return array|bool Queried column or FALSE on failure. + */ + public function queryColumn( + $queryName, $params = [], $limit = -1, $offset = 0 + ) { + $result = $this->execQuery($queryName, $params, $limit, $offset); + if ($result === false) { + return false; + } + + $column = $result->fetchAll(\PDO::FETCH_COLUMN); + return $column; + } + + /** + * Fetch entity returned by the given query. + * + * @param string $queryName The query to execute. + * @param string $entityClass The entity class name. + * @param array $params The query parameters to bind. + * + * @return mixed|null The queried entity, NULL if it does not exists or + * FALSE on failure. + */ + public function queryEntity($queryName, $entityClass, $params = []) + { + $result = $this->execQuery($queryName, $params); + if ($result === false) { + return false; + } + + $result->setFetchMode(\PDO::FETCH_CLASS, $entityClass); + $entity = $result->fetch(); + + if ($entity === false) { + return null; + } + + if (empty($entity) === true) { + $this->logger->debug( + "Empty result for query: " . $queryName, + ["app" => $this->appName] + ); + return null; + } + + return $entity; + } + + /** + * Fetch entities returned by the given query. + * + * @param string $queryName The query to execute. + * @param string $entityClass The entity class name. + * @param array $params The query parameters to bind. + * @param int $limit Results limit. Defaults to -1 (no limit). + * @param int $offset Results offset. Defaults to 0. + * + * @return mixed|null The queried entities or FALSE on failure. + */ + public function queryEntities( + $queryName, $entityClass, $params = [], $limit = -1, $offset = 0 + ) { + $result = $this->execQuery($queryName, $params, $limit, $offset); + if ($result === false) { + return false; + } + + $result->setFetchMode(\PDO::FETCH_CLASS, $entityClass); + $entities = $result->fetchAll(); + + return $entities; + } +} diff --git a/lib/Query/QueryProvider.php b/lib/Query/QueryProvider.php new file mode 100644 index 0000000..c1fdd52 --- /dev/null +++ b/lib/Query/QueryProvider.php @@ -0,0 +1,195 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Query; + +use OCA\UserSQL\Constant\DB; +use OCA\UserSQL\Constant\Query; +use OCA\UserSQL\Properties; + +/** + * Provides queries array. + * + * @author Marcin Łojewski + */ +class QueryProvider implements \ArrayAccess +{ + /** + * @var Properties The properties array. + */ + private $properties; + /** + * @var array The queries array. + */ + private $queries; + + /** + * The class constructor. + * + * @param Properties $properties The properties array. + */ + public function __construct(Properties $properties) + { + $this->properties = $properties; + $this->loadQueries(); + } + + /** + * Load queries to the array. + */ + private function loadQueries() + { + $group = $this->properties[DB::GROUP_TABLE]; + $userGroup = $this->properties[DB::USER_GROUP_TABLE]; + $user = $this->properties[DB::USER_TABLE]; + + $gAdmin = $this->properties[DB::GROUP_ADMIN_COLUMN]; + $gGID = $this->properties[DB::GROUP_GID_COLUMN]; + $gName = $this->properties[DB::GROUP_NAME_COLUMN]; + + $uAvatar = $this->properties[DB::USER_AVATAR_COLUMN]; + $uEmail = $this->properties[DB::USER_EMAIL_COLUMN]; + $uHome = $this->properties[DB::USER_HOME_COLUMN]; + $uName = $this->properties[DB::USER_NAME_COLUMN]; + $uPassword = $this->properties[DB::USER_PASSWORD_COLUMN]; + $uUID = $this->properties[DB::USER_UID_COLUMN]; + + $ugGID = $this->properties[DB::USER_GROUP_GID_COLUMN]; + $ugUID = $this->properties[DB::USER_GROUP_UID_COLUMN]; + + $gidParam = Query::GID_PARAM; + $nameParam = Query::NAME_PARAM; + $passwordParam = Query::PASSWORD_PARAM; + $searchParam = Query::SEARCH_PARAM; + $uidParam = Query::UID_PARAM; + + $groupColumns + = "$gGID AS gid, " . + (empty($gName) ? "null" : $gName) . " AS name, " . + (empty($gAdmin) ? "false" : $gAdmin) . " AS admin"; + $userColumns + = "$uUID AS uid, " . + (empty($uName) ? "null" : $uName) . " AS name, " . + (empty($uEmail) ? "null" : $uEmail) . " AS email, " . + (empty($uHome) ? "null" : $uHome) . " AS home, " . + (empty($uAvatar) ? "false" : $uAvatar) . " AS avatar"; + + $this->queries = [ + Query::BELONGS_TO_ADMIN => + "SELECT COUNT($gGID) > 0 AS admin " . + "FROM $group, $userGroup " . + "WHERE $ugGID = $gGID " . + "AND $ugUID = :$uidParam " . + "AND $gAdmin", + + Query::COUNT_GROUPS => + "SELECT COUNT($ugGID) " . + "FROM $userGroup " . + "WHERE $ugGID = :$gidParam " . + "AND $ugUID " . + "LIKE :$searchParam", + + Query::COUNT_USERS => + "SELECT COUNT($uUID) AS count " . + "FROM $user " . + "WHERE $uUID LIKE :$searchParam", + + Query::FIND_GROUP => + "SELECT $groupColumns " . + "FROM $group " . + "WHERE $gGID = :$gidParam", + + Query::FIND_GROUP_USERS => + "SELECT $ugUID AS uid " . + "FROM $userGroup " . + "WHERE $ugGID = :$gidParam " . + "AND $ugUID " . + "LIKE :$searchParam " . + "ORDER BY $ugUID", + + Query::FIND_GROUPS => + "SELECT $groupColumns " . + "FROM $group " . + "WHERE $gGID LIKE :$searchParam " . + "ORDER BY $gGID", + + Query::FIND_USER => + "SELECT $userColumns, $uPassword AS password " . + "FROM $user " . + "WHERE $uUID = :$uidParam", + + Query::FIND_USER_GROUPS => + "SELECT $groupColumns " . + "FROM $group, $userGroup " . + "WHERE $ugGID = $gGID " . + "AND $ugUID = :$uidParam " . + "ORDER BY $gGID", + + Query::FIND_USERS => + "SELECT $userColumns " . + "FROM $user " . + "WHERE $uUID LIKE :$searchParam " . + "ORDER BY $uUID", + + Query::SAVE_USER => + "UPDATE $user " . + "SET $uPassword = :$passwordParam, " . + "$uName = :$nameParam " . + "WHERE $uUID = :$uidParam", + ]; + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return isset($this->queries[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + if (isset($this->queries[$offset])) { + return $this->queries[$offset]; + } else { + return null; + } + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->queries[$offset] = $value; + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->queries[$offset]); + } +} diff --git a/lib/Repository/GroupRepository.php b/lib/Repository/GroupRepository.php new file mode 100644 index 0000000..f0203c3 --- /dev/null +++ b/lib/Repository/GroupRepository.php @@ -0,0 +1,150 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Repository; + +use OCA\UserSQL\Constant\Query; +use OCA\UserSQL\Model\Group; +use OCA\UserSQL\Query\DataQuery; + +/** + * The group repository. + * + * @author Marcin Łojewski + */ +class GroupRepository +{ + /** + * @var DataQuery The data query object. + */ + private $dataQuery; + + /** + * The class constructor. + * + * @param DataQuery $dataQuery The data query object. + */ + public function __construct(DataQuery $dataQuery) + { + $this->dataQuery = $dataQuery; + } + + /** + * Get a group entity object. + * + * @param string $gid The group ID. + * + * @return Group The group entity, NULL if it does not exists or + * FALSE on failure. + */ + public function findByGid($gid) + { + return $this->dataQuery->queryEntity( + Query::FIND_GROUP, Group::class, [Query::GID_PARAM => $gid] + ); + } + + /** + * Get all groups a user belongs to. + * + * @param string $uid The user ID. + * + * @return Group[] Array of group entity objects or FALSE on failure. + */ + public function findAllByUid($uid) + { + return $this->dataQuery->queryEntities( + Query::FIND_USER_GROUPS, Group::class, [Query::UID_PARAM => $uid] + ); + } + + /** + * Get a list of all user IDs belonging to the group. + * + * @param string $gid The group ID. + * @param string $search The UID search term. Defaults to "" (empty string). + * @param int $limit (optional) Results limit. + * Defaults to -1 (no limit). + * @param int $offset (optional) Results offset. Defaults to 0. + * + * @return string[] Array of UIDs belonging to the group + * or FALSE on failure. + */ + public function findAllUidsBySearchTerm( + $gid, $search = "", $limit = -1, $offset = 0 + ) { + return $this->dataQuery->queryColumn( + Query::FIND_GROUP_USERS, + [Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search], $limit, + $offset + ); + } + + /** + * Get an array of group entity objects. + * + * @param string $search The search term. Defaults to "" (empty string). + * @param int $limit (optional) Results limit. + * Defaults to -1 (no limit). + * @param int $offset (optional) Results offset. Defaults to 0. + * + * @return Group[] Array of group entity objects or FALSE on failure. + */ + public function findAllBySearchTerm($search = "", $limit = -1, $offset = 0) + { + return $this->dataQuery->queryEntities( + Query::FIND_GROUPS, Group::class, [Query::SEARCH_PARAM => $search], + $limit, $offset + ); + } + + /** + * Get the number of users in given group matching the search term. + * + * @param string $gid The group ID. + * @param string $search The UID search term. Defaults to "" (empty string). + * + * @return int The number of users in given group matching the search term + * or FALSE on failure. + */ + public function countAll($gid, $search = "") + { + return $this->dataQuery->queryValue( + Query::COUNT_GROUPS, + [Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search] + ); + } + + /** + * Find out if the user belongs to any admin group. + * + * @param string $uid The user ID. + * + * @return bool|null TRUE if the user belongs to any admin group, + * FALSE if not, NULL on failure. + */ + public function belongsToAdmin($uid) + { + return $this->dataQuery->queryValue( + Query::BELONGS_TO_ADMIN, [Query::UID_PARAM => $uid], null + ); + } +} diff --git a/lib/Repository/UserRepository.php b/lib/Repository/UserRepository.php new file mode 100644 index 0000000..8f284b6 --- /dev/null +++ b/lib/Repository/UserRepository.php @@ -0,0 +1,114 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Repository; + +use OCA\UserSQL\Constant\Query; +use OCA\UserSQL\Model\User; +use OCA\UserSQL\Query\DataQuery; + +/** + * The user repository. + * + * @author Marcin Łojewski + */ +class UserRepository +{ + /** + * @var DataQuery The data query object. + */ + private $dataQuery; + + /** + * The class constructor. + * + * @param DataQuery $dataQuery The data query object. + */ + public function __construct(DataQuery $dataQuery) + { + $this->dataQuery = $dataQuery; + } + + /** + * Get a user entity object. + * + * @param string $uid The user ID. + * + * @return User The user entity, NULL if it does not exists or + * FALSE on failure. + */ + public function findByUid($uid) + { + return $this->dataQuery->queryEntity( + Query::FIND_USER, User::class, [Query::UID_PARAM => $uid] + ); + } + + /** + * Get an array of user entity objects. + * + * @param string $search The search term. Defaults to "" (empty string). + * @param int $limit (optional) Results limit. + * Defaults to -1 (no limit). + * @param int $offset (optional) Results offset. Defaults to 0. + * + * @return User[] Array of user entity objects or FALSE on failure. + */ + public function findAllBySearchTerm($search = "", $limit = -1, $offset = 0) + { + return $this->dataQuery->queryEntities( + Query::FIND_USERS, User::class, [Query::SEARCH_PARAM => $search], + $limit, $offset + ); + } + + /** + * Get the number of users. + * + * @param string $search The search term. Defaults to "" (empty string). + * + * @return int The number of users or FALSE on failure. + */ + public function countAll($search = "") + { + return $this->dataQuery->queryValue( + Query::COUNT_USERS, [Query::SEARCH_PARAM => $search] + ); + } + + /** + * Save an user entity object. + * + * @param User $user The user entity. + * + * @return bool TRUE on success, FALSE otherwise. + */ + public function save($user) + { + return $this->dataQuery->update( + Query::SAVE_USER, [ + Query::NAME_PARAM => $user->name, + Query::PASSWORD_PARAM => $user->password, + Query::UID_PARAM => $user->uid + ] + ); + } +} diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index cf11109..e578dc3 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -1,10 +1,9 @@ + * @author Marcin Łojewski * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,84 +16,64 @@ * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * + * along with this program. If not, see . */ -namespace OCA\user_sql\Settings; +namespace OCA\UserSQL\Settings; +use OCA\UserSQL\Properties; use OCP\AppFramework\Http\TemplateResponse; -use OCP\Defaults; -use OCP\IConfig; -use OCP\IL10N; use OCP\Settings\ISettings; -use OCA\user_sql\lib\Helper; - -class Admin implements ISettings { - /** @var IL10N */ - private $l10n; - /** @var Defaults */ - private $defaults; - /** @var IConfig */ - private $config; - - /** - * @param IL10N $l10n - * @param Defaults $defaults - * @param IConfig $config - */ - public function __construct(IL10N $l10n, - Defaults $defaults, - IConfig $config) { - $this->l10n = $l10n; - $this->defaults = $defaults; - $this->config = $config; - $this->helper = new \OCA\user_sql\lib\Helper(); - $this->params = $this->helper->getParameterArray(); - $this->settings = $this->helper -> loadSettingsForDomain('default'); - } - - /** - * @return TemplateResponse - */ - public function getForm() { - $type = $this->config->getAppValue('user_sql', 'type'); - $trusted_domains = \OC::$server->getConfig()->getSystemValue('trusted_domains'); - $inserted = array('default'); - array_splice($trusted_domains, 0, 0, $inserted); - - $params = [ - 'type' => $type, - ]; - $params['allowed_domains']= array_unique($trusted_domains); - - foreach($this->params as $key) - { - $value = $this->settings[$key]; - $params[$key]=$value; - } +/** + * The administrator's settings page. + * + * @author Marcin Łojewski + */ +class Admin implements ISettings +{ + /** + * @var string The application name. + */ + private $appName; + /** + * @var Properties The properties array. + */ + private $properties; - $params["config"]=$this->config; - return new TemplateResponse('user_sql', 'admin', $params); - } + /** + * The class constructor, + * + * @param string $AppName The application name. + * @param Properties $properties The properties array. + */ + public function __construct($AppName, Properties $properties) + { + $this->appName = $AppName; + $this->properties = $properties; + } - /** - * @return string the section ID, e.g. 'sharing' - */ - public function getSection() { - return 'usersql'; - } + /** + * @inheritdoc + */ + public function getForm() + { + return new TemplateResponse($this->appName, "admin", $this->properties->getArray()); + } - /** - * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. - * - * keep the server setting at the top, right after "server settings" - */ - public function getPriority() { - return 0; - } + /** + * @inheritdoc + */ + public function getSection() + { + return $this->appName; + } + /** + * @inheritdoc + */ + public function getPriority() + { + return 25; + } } diff --git a/lib/Settings/Section.php b/lib/Settings/Section.php index 091f8f2..a3dfc3a 100644 --- a/lib/Settings/Section.php +++ b/lib/Settings/Section.php @@ -1,10 +1,9 @@ + * @author Marcin Łojewski * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,52 +16,79 @@ * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * + * along with this program. If not, see . */ -namespace OCA\user_sql\Settings; +namespace OCA\UserSQL\Settings; use OCP\IL10N; -use OCP\Settings\IIconSection; use OCP\IURLGenerator; +use OCP\Settings\IIconSection; + +/** + * The section item. + * + * @author Marcin Łojewski + */ +class Section implements IIconSection +{ + /** + * @var string The application name. + */ + private $appName; + /** + * @var IURLGenerator The URL generator. + */ + private $urlGenerator; + /** + * @var IL10N The localization service. + */ + private $localization; -class Section implements IIconSection { - /** @var IL10N */ - private $l; + /** + * The class constructor. + * + * @param string $AppName The application name. + * @param IURLGenerator $urlGenerator The URL generator. + * @param IL10N $localization The localization service. + */ + public function __construct( + $AppName, IURLGenerator $urlGenerator, IL10N $localization + ) { + $this->appName = $AppName; + $this->urlGenerator = $urlGenerator; + $this->localization = $localization; + } - /** - * @param IL10N $l - */ - public function __construct(IURLGenerator $url,IL10N $l) { - $this->l = $l; - $this->url = $url; - } + /** + * @inheritdoc + */ + public function getID() + { + return $this->appName; + } - /** - * {@inheritdoc} - */ - public function getID() { - return 'usersql'; - } + /** + * @inheritdoc + */ + public function getName() + { + return $this->localization->t("SQL Backends"); + } - /** - * {@inheritdoc} - */ - public function getName() { - return $this->l->t('User SQL'); - } + /** + * @inheritdoc + */ + public function getPriority() + { + return 25; + } - /** - * {@inheritdoc} - */ - public function getPriority() { - return 75; - } - /** - * {@inheritdoc} - */ - public function getIcon() { - return $this->url->imagePath('user_sql', 'app-dark.svg'); - } + /** + * @inheritdoc + */ + public function getIcon() + { + return $this->urlGenerator->imagePath($this->appName, "app-dark.svg"); + } } diff --git a/lib/drupal.php b/lib/drupal.php deleted file mode 100644 index 4c7e0a4..0000000 --- a/lib/drupal.php +++ /dev/null @@ -1,321 +0,0 @@ -> 6) & 0x3f]; - if ($i++ >= $count) { - break; - } - if ($i < $count) { - $value |= ord($input[$i]) << 16; - } - $output .= $itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) { - break; - } - $output .= $itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); - - return $output; -} -/** - * Returns a string of highly randomized bytes (over the full 8-bit range). - * - * This function is better than simply calling mt_rand() or any other built-in - * PHP function because it can return a long string of bytes (compared to < 4 - * bytes normally from mt_rand()) and uses the best available pseudo-random - * source. - * - * @param $count - * The number of characters (bytes) to return in the string. - */ - - function _random_bytes($count) { - // $random_state does not use static as it stores random bytes. - static $random_state, $bytes, $has_openssl; - - $missing_bytes = $count - strlen($bytes); - - if ($missing_bytes > 0) { - // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes() - // locking on Windows and rendered it unusable. - if (!isset($has_openssl)) { - $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes'); - } - - // openssl_random_pseudo_bytes() will find entropy in a system-dependent - // way. - if ($has_openssl) { - $bytes .= openssl_random_pseudo_bytes($missing_bytes); - } - - // Else, read directly from /dev/urandom, which is available on many *nix - // systems and is considered cryptographically secure. - elseif ($fh = @fopen('/dev/urandom', 'rb')) { - // PHP only performs buffered reads, so in reality it will always read - // at least 4096 bytes. Thus, it costs nothing extra to read and store - // that much so as to speed any additional invocations. - $bytes .= fread($fh, max(4096, $missing_bytes)); - fclose($fh); - } - - // If we couldn't get enough entropy, this simple hash-based PRNG will - // generate a good set of pseudo-random bytes on any system. - // Note that it may be important that our $random_state is passed - // through hash() prior to being rolled into $output, that the two hash() - // invocations are different, and that the extra input into the first one - - // the microtime() - is prepended rather than appended. This is to avoid - // directly leaking $random_state via the $output stream, which could - // allow for trivial prediction of further "random" numbers. - if (strlen($bytes) < $count) { - // Initialize on the first call. The contents of $_SERVER includes a mix of - // user-specific and system information that varies a little with each page. - if (!isset($random_state)) { - $random_state = print_r($_SERVER, TRUE); - if (function_exists('getmypid')) { - // Further initialize with the somewhat random PHP process ID. - $random_state .= getmypid(); - } - $bytes = ''; - } - - do { - $random_state = hash('sha256', microtime() . mt_rand() . $random_state); - $bytes .= hash('sha256', mt_rand() . $random_state, TRUE); - } - while (strlen($bytes) < $count); - } - } - $output = substr($bytes, 0, $count); - $bytes = substr($bytes, $count); - return $output; -} - -/** - * Generates a random base 64-encoded salt prefixed with settings for the hash. - * - * Proper use of salts may defeat a number of attacks, including: - * - The ability to try candidate passwords against multiple hashes at once. - * - The ability to use pre-hashed lists of candidate passwords. - * - The ability to determine whether two users have the same (or different) - * password without actually having to guess one of the passwords. - * - * @param $count_log2 - * Integer that determines the number of iterations used in the hashing - * process. A larger value is more secure, but takes more time to complete. - * - * @return - * A 12 character string containing the iteration count and a random salt. - */ -function _password_generate_salt($count_log2) { - $output = '$S$'; - // Ensure that $count_log2 is within set bounds. - $count_log2 = _password_enforce_log2_boundaries($count_log2); - // We encode the final log2 iteration count in base 64. - $itoa64 = _password_itoa64(); - $output .= $itoa64[$count_log2]; - // 6 bytes is the standard salt for a portable phpass hash. - $output .= _password_base64_encode(_random_bytes(6), 6); - return $output; -} - -/** - * Ensures that $count_log2 is within set bounds. - * - * @param $count_log2 - * Integer that determines the number of iterations used in the hashing - * process. A larger value is more secure, but takes more time to complete. - * - * @return - * Integer within set bounds that is closest to $count_log2. - */ -function _password_enforce_log2_boundaries($count_log2) { - if ($count_log2 < MIN_HASH_COUNT) { - return MIN_HASH_COUNT; - } - elseif ($count_log2 > MAX_HASH_COUNT) { - return MAX_HASH_COUNT; - } - - return (int) $count_log2; -} - -/** - * Hash a password using a secure stretched hash. - * - * By using a salt and repeated hashing the password is "stretched". Its - * security is increased because it becomes much more computationally costly - * for an attacker to try to break the hash by brute-force computation of the - * hashes of a large number of plain-text words or strings to find a match. - * - * @param $algo - * The string name of a hashing algorithm usable by hash(), like 'sha256'. - * @param $password - * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash. - * @param $setting - * An existing hash or the output of _password_generate_salt(). Must be - * at least 12 characters (the settings and salt). - * - * @return - * A string containing the hashed password (and salt) or FALSE on failure. - * The return string will be truncated at DRUPAL_HASH_LENGTH characters max. - */ -function _password_crypt($algo, $password, $setting) { - // Prevent DoS attacks by refusing to hash large passwords. - if (strlen($password) > 512) { - return FALSE; - } - // The first 12 characters of an existing hash are its setting string. - $setting = substr($setting, 0, 12); - - if ($setting[0] != '$' || $setting[2] != '$') { - return FALSE; - } - $count_log2 = _password_get_count_log2($setting); - // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT - if ($count_log2 < MIN_HASH_COUNT || $count_log2 > MAX_HASH_COUNT) { - return FALSE; - } - $salt = substr($setting, 4, 8); - // Hashes must have an 8 character salt. - if (strlen($salt) != 8) { - return FALSE; - } - - // Convert the base 2 logarithm into an integer. - $count = 1 << $count_log2; - - // We rely on the hash() function being available in PHP 5.2+. - $hash = hash($algo, $salt . $password, TRUE); - do { - $hash = hash($algo, $hash . $password, TRUE); - } while (--$count); - - $len = strlen($hash); - $output = $setting . _password_base64_encode($hash, $len); - // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. - // _password_base64_encode() of a 64 byte sha512 will always be 86 characters. - $expected = 12 + ceil((8 * $len) / 6); - return (strlen($output) == $expected) ? substr($output, 0, HASH_LENGTH) : FALSE; -} - -/** - * Parse the log2 iteration count from a stored hash or setting string. - */ -function _password_get_count_log2($setting) { - $itoa64 = _password_itoa64(); - return strpos($itoa64, $setting[3]); -} - -/** - * Hash a password using a secure hash. - * - * @param $password - * A plain-text password. - * @param $count_log2 - * Optional integer to specify the iteration count. Generally used only during - * mass operations where a value less than the default is needed for speed. - * - * @return - * A string containing the hashed password (and a salt), or FALSE on failure. - */ -function user_hash_password($password, $count_log2 = 0) { - if (empty($count_log2)) { - // Use the standard iteration count. - $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT); - } - return _password_crypt('sha512', $password, _password_generate_salt($count_log2)); -} - -/** - * Check whether a plain text password matches a stored hashed password. - * - * @param $password - * A plain-text password - * @param $hashpass - * - * @return - * TRUE or FALSE. - */ -function user_check_password($password, $hashpass) { - $stored_hash = $hashpass; - $type = substr($stored_hash, 0, 3); - switch ($type) { - case '$S$': - // A normal Drupal 7 password using sha512. - $hash = _password_crypt('sha512', $password, $stored_hash); - break; - case '$H$': - // phpBB3 uses "$H$" for the same thing as "$P$". - case '$P$': - // A phpass password generated using md5. This is an - // imported password or from an earlier Drupal version. - $hash = _password_crypt('md5', $password, $stored_hash); - break; - default: - return FALSE; - } - return ($hash && $stored_hash == $hash); -} \ No newline at end of file diff --git a/lib/group_sql.php b/lib/group_sql.php deleted file mode 100644 index b0c1c06..0000000 --- a/lib/group_sql.php +++ /dev/null @@ -1,96 +0,0 @@ - helper = new \OCA\user_sql\lib\Helper(); - $domain = \OC::$server->getRequest()->getServerHost(); - $this -> settings = $this -> helper -> loadSettingsForDomain($domain); - $this -> helper -> connectToDb($this -> settings); - return false; - } - - public function getUserGroups($uid) { - if(empty($this -> settings['sql_group_table'])) - { - Util::writeLog('OC_USER_SQL', "Group table not configured", Util::DEBUG); - return []; - } - $rows = $this -> helper -> runQuery('getUserGroups', array('uid' => $uid), false, true); - if($rows === false) - { - Util::writeLog('OC_USER_SQL', "Found no group", Util::DEBUG); - return []; - } - $groups = array(); - foreach($rows as $row) - { - $groups[] = $row[$this -> settings['col_group_name']]; - } - return $groups; - } - - public function getGroups($search = '', $limit = null, $offset = null) { - if(empty($this -> settings['sql_group_table'])) - { - return []; - } - $search = "%".$search."%"; - $rows = $this -> helper -> runQuery('getGroups', array('search' => $search), false, true, array('limit' => $limit, 'offset' => $offset)); - if($rows === false) - { - return []; - } - $groups = array(); - foreach($rows as $row) - { - $groups[] = $row[$this -> settings['col_group_name']]; - } - return $groups; - } - - public function usersInGroup($gid, $search = '', $limit = null, $offset = null) { - if(empty($this -> settings['sql_group_table'])) - { - Util::writeLog('OC_USER_SQL', "Group table not configured", Util::DEBUG); - return []; - } - $rows = $this -> helper -> runQuery('getGroupUsers', array('gid' => $gid), false, true); - if($rows === false) - { - Util::writeLog('OC_USER_SQL', "Found no users for group", Util::DEBUG); - return []; - } - $users = array(); - foreach($rows as $row) - { - $users[] = $row[$this -> settings['col_group_username']]; - } - return $users; - } - - public function countUsersInGroup($gid, $search = '') { - if(empty($this -> settings['sql_group_table'])) - { - return 0; - } - $search = "%".$search."%"; - $count = $this -> helper -> runQuery('countUsersInGroup', array('gid' => $gid, 'search' => $search)); - if($count === false) - { - return 0; - } else { - return intval(reset($count)); - } - } -} -?> diff --git a/lib/helper.php b/lib/helper.php deleted file mode 100644 index 0c0f782..0000000 --- a/lib/helper.php +++ /dev/null @@ -1,435 +0,0 @@ - - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see . - * - */ - -namespace OCA\user_sql\lib; -use OCP\IConfig; -use OCP\Util; - -class Helper { - - protected $db; - protected $db_conn; - protected $settings; - - /** - * The default constructor initializes some parameters - */ - public function __construct() - { - $this->db_conn = false; - } - - /** - * Return an array with all supported parameters - * @return array Containing strings of the parameters - */ - public function getParameterArray() - { - $params = array( - 'sql_hostname', - 'sql_username', - 'sql_password', - 'sql_database', - 'sql_table', - 'sql_driver', - 'col_username', - 'col_password', - 'col_active', - 'col_displayname', - 'col_email', - 'col_gethome', - 'set_active_invert', - 'set_allow_pwchange', - 'set_default_domain', - 'set_strip_domain', - 'set_crypt_type', - 'set_mail_sync_mode', - 'set_enable_gethome', - 'set_gethome_mode', - 'set_gethome', - 'sql_group_table', - 'col_group_username', - 'col_group_name' - ); - - return $params; - } - - /** - * Load the settings for a given domain. If the domain is not found, - * the settings for 'default' are returned instead. - * @param string $domain The domain name - * @return array of settings - */ - public function loadSettingsForDomain($domain) - { - Util::writeLog('OC_USER_SQL', "Trying to load settings for domain: " . $domain, Util::DEBUG); - $settings = array(); - $sql_host = \OC::$server->getConfig()->getAppValue('user_sql', 'sql_hostname_'.$domain, ''); - if($sql_host === '') - { - $domain = 'default'; - } - $params = $this -> getParameterArray(); - foreach($params as $param) - { - $settings[$param] = \OC::$server->getConfig()->getAppValue('user_sql', $param.'_'.$domain, ''); - } - Util::writeLog('OC_USER_SQL', "Loaded settings for domain: " . $domain, Util::DEBUG); - return $settings; - } - - /** - * Run a given query type and return the results - * @param string $type The type of query to run - * @param array $params The parameter array of the query (i.e. the values to bind as key-value pairs) - * @param bool $execOnly Only execute the query, but don't fetch the results (optional, default = false) - * @param bool $fetchArray Fetch an array instead of a single row (optional, default=false) - * @param array $limits use the given limits for the query (optional, default = empty) - * @return mixed - */ - public function runQuery($type, $params, $execOnly = false, $fetchArray = false, $limits = array()) - { - Util::writeLog('OC_USER_SQL', "Entering runQuery for type: " . $type, Util::DEBUG); - if(!$this -> db_conn) - return false; - - switch($type) - { - case 'getHome': - $query = "SELECT ".$this->settings['col_gethome']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid"; - break; - case 'getMail': - $query = "SELECT ".$this->settings['col_email']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid"; - break; - - case 'setMail': - $query = "UPDATE ".$this->settings['sql_table']." SET ".$this->settings['col_email']." = :currMail WHERE ".$this->settings['col_username']." = :uid"; - break; - - case 'getPass': - $query = "SELECT ".$this->settings['col_password']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid"; - if($this -> settings['col_active'] !== '') - $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active']; - break; - - case 'setPass': - $query = "UPDATE ".$this->settings['sql_table']." SET ".$this->settings['col_password']." = :enc_password WHERE ".$this->settings['col_username'] ." = :uid"; - break; - - case 'getRedmineSalt': - $query = "SELECT salt FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username'] ." = :uid;"; - break; - - case 'countUsers': - $query = "SELECT COUNT(*) FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username'] ." LIKE :search"; - if($this -> settings['col_active'] !== '') - $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active']; - break; - - case 'getUsers': - $query = "SELECT ".$this->settings['col_username']." FROM ".$this->settings['sql_table']; - $query .= " WHERE ".$this->settings['col_username']." LIKE :search"; - if($this -> settings['col_active'] !== '') - $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active']; - $query .= " ORDER BY ".$this->settings['col_username']; - break; - - case 'userExists': - $query = "SELECT ".$this->settings['col_username']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid"; - if($this -> settings['col_active'] !== '') - $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active']; - break; - - case 'getDisplayName': - $query = "SELECT ".$this->settings['col_displayname']." FROM ".$this->settings['sql_table']." WHERE ".$this->settings['col_username']." = :uid"; - if($this -> settings['col_active'] !== '') - $query .= " AND " .($this -> settings['set_active_invert'] === 'true' ? "NOT " : "" ) . $this -> settings['col_active']; - break; - - case 'mysqlEncryptSalt': - $query = "SELECT ENCRYPT(:pw, :salt);"; - break; - - case 'mysqlEncrypt': - $query = "SELECT ENCRYPT(:pw);"; - break; - - case 'mysqlPassword': - $query = "SELECT PASSWORD(:pw);"; - break; - - case 'getUserGroups': - $query = "SELECT ".$this->settings['col_group_name']." FROM ".$this->settings['sql_group_table']." WHERE ".$this->settings['col_group_username']." = :uid"; - break; - - case 'getGroups': - $query = "SELECT distinct ".$this->settings['col_group_name']." FROM ".$this->settings['sql_group_table']." WHERE ".$this->settings['col_group_name']." LIKE :search"; - break; - - case 'getGroupUsers': - $query = "SELECT distinct ".$this->settings['col_group_username']." FROM ".$this->settings['sql_group_table']." WHERE ".$this->settings['col_group_name']." = :gid"; - break; - - case 'countUsersInGroup': - $query = "SELECT count(".$this->settings['col_group_username'].") FROM ".$this->settings['sql_group_table']." WHERE ".$this->settings['col_group_name']." = :gid AND ".$this->settings['col_group_username']." LIKE :search"; - break; - } - - if(isset($limits['limit']) && $limits['limit'] !== null) - { - $limit = intval($limits['limit']); - $query .= " LIMIT ".$limit; - } - - if(isset($limits['offset']) && $limits['offset'] !== null) - { - $offset = intval($limits['offset']); - $query .= " OFFSET ".$offset; - } - - Util::writeLog('OC_USER_SQL', "Preparing query: $query", Util::DEBUG); - $result = $this -> db -> prepare($query); - foreach($params as $param => $value) - { - $result -> bindValue(":".$param, $value); - } - Util::writeLog('OC_USER_SQL', "Executing query...", Util::DEBUG); - if(!$result -> execute()) - { - $err = $result -> errorInfo(); - Util::writeLog('OC_USER_SQL', "Query failed: " . $err[2], Util::DEBUG); - return false; - } - if($execOnly === true) - { - return true; - } - Util::writeLog('OC_USER_SQL', "Fetching result...", Util::DEBUG); - if($fetchArray === true) - $row = $result -> fetchAll(); - else - $row = $result -> fetch(); - - if(!$row) - { - return false; - } - return $row; - } - - /** - * Connect to the database using ownCloud's DBAL - * @param array $settings The settings for the connection - * @return bool - */ - public function connectToDb($settings) - { - $this -> settings = $settings; - $cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig()); - $parameters = array('host' => $this -> settings['sql_hostname'], - 'password' => $this -> settings['sql_password'], - 'user' => $this -> settings['sql_username'], - 'dbname' => $this -> settings['sql_database'], - 'tablePrefix' => '' - ); - try - { - $this -> db = $cm -> getConnection($this -> settings['sql_driver'], $parameters); - $this -> db -> query("SET NAMES 'UTF8'"); - $this -> db_conn = true; - return true; - } - catch (\Exception $e) - { - Util::writeLog('OC_USER_SQL', 'Failed to connect to the database: ' . $e -> getMessage(), Util::ERROR); - $this -> db_conn = false; - return false; - } - } - - /** - * Check if all of the given columns exist - * @param array $parameters The connection parameters - * @param string $sql_driver The SQL driver to use - * @param string $table The table name to check - * @param array $cols The columns to check - * @param array True if found, otherwise false - */ - public function verifyColumns($parameters, $sql_driver, $table, $cols) - { - $columns = $this->getColumns($parameters, $sql_driver, $table); - $res = true; - $err = ''; - foreach($cols as $col) - { - if(!in_array($col, $columns, true)) - { - $res = false; - $err .= $col.' '; - } - } - if($res) - return true; - else - return $err; - } - - /** - * Check if a given table exists - * @param array $parameters The connection parameters - * @param string $sql_driver The SQL driver to use - * @param string $table The table name to check - * @param array True if found, otherwise false - */ - public function verifyTable($parameters, $sql_driver, $table) - { - $tablesWithSchema = $this->getTables($parameters, $sql_driver, true); - $tablesWithoutSchema = $this->getTables($parameters, $sql_driver, false); - return in_array($table, $tablesWithSchema, true) || in_array($table, $tablesWithoutSchema, true); - } - - /** - * Retrieve a list of tables for the given connection parameters - * @param array $parameters The connection parameters - * @param string $sql_driver The SQL driver to use - * @param boolean $schema Return table name with schema - * @return array The found tables, empty if an error occurred - */ - public function getTables($parameters, $sql_driver, $schema = true) - { - $cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig()); - try { - $conn = $cm -> getConnection($sql_driver, $parameters); - $platform = $conn -> getDatabasePlatform(); - - $queryTables = $platform->getListTablesSQL(); - $queryViews = $platform->getListViewsSQL($parameters['dbname']); - $ret = array(); - - $result = $conn->executeQuery($queryTables); - while ($row = $result->fetch()) { - $name = $this->getTableNameFromRow($sql_driver, $parameters['dbname'], $row, $schema); - $ret[] = $name; - } - - $result = $conn->executeQuery($queryViews); - while ($row = $result->fetch()) { - $name = $this->getViewNameFromRow($sql_driver, $row, $schema); - $ret[] = $name; - } - return $ret; - } - catch(\Exception $e) - { - return array(); - } - } - - /** - * Retrieve table name from database list table SQL - * @param string $sql_driver The SQL driver to use - * @param string $dbname The database name - * @param array $row Query result row - * @param boolean $schema Return table name with schema - * @return string Table name - */ - public function getTableNameFromRow($sql_driver, $dbname, $row, $schema) - { - switch ($sql_driver) { - case 'mysql': - return $row['Tables_in_' . $dbname]; - case 'pgsql': - if ($schema) { - return $row['schema_name'] . '.' . $row['table_name']; - } else { - return $row['table_name']; - } - default: - return null; - } - } - - /** - * Retrieve view name from database list table SQL - * @param string $sql_driver The SQL driver to use - * @param array $row Query result row - * @param boolean $schema Return table name with schema - * @return string Table name - */ - public function getViewNameFromRow($sql_driver, $row, $schema) - { - switch ($sql_driver) { - case 'mysql': - return $row['TABLE_NAME']; - case 'pgsql': - if ($schema) { - return $row['schemaname'] . '.' . $row['viewname']; - } else { - return $row['viewname']; - } - default: - return null; - } - } - - /** - * Retrieve a list of columns for the given connection parameters - * @param array $parameters The connection parameters - * @param string $sql_driver The SQL driver to use - * @param string $table The SQL table to work with - * @return array The found column, empty if an error occured - */ - public function getColumns($parameters, $sql_driver, $table) - { - $cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig()); - try { - $conn = $cm -> getConnection($sql_driver, $parameters); - $platform = $conn -> getDatabasePlatform(); - $query = $platform -> getListTableColumnsSQL($table); - $result = $conn -> executeQuery($query); - $ret = array(); - while($row = $result -> fetch()) - { - switch ($sql_driver) { - case 'mysql': - $name = $row['Field']; - break; - case 'pgsql': - $name = $row['field']; - break; - default: - return $ret; - } - $ret[] = $name; - } - return $ret; - } - catch(\Exception $e) - { - return array(); - } - } - - -} diff --git a/lib/user_sql.php b/lib/user_sql.php deleted file mode 100644 index 936dfbb..0000000 --- a/lib/user_sql.php +++ /dev/null @@ -1,1034 +0,0 @@ - - * - * credits go to Ed W for several SQL injection fixes and caching support - * credits go to Frédéric France for providing Joomla support - * credits go to Mark Jansenn for providing Joomla 2.5.18+ / 3.2.1+ support - * credits go to Dominik Grothaus for providing SSHA256 support and fixing a few bugs - * credits go to Sören Eberhardt-Biermann for providing multi-host support - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see . - * - */ - -namespace OCA\user_sql; -use OC\User\Backend; - -use \OCA\user_sql\lib\Helper; -use OCP\IConfig; -use OCP\IUser; -use OCP\IUserSession; -use OCP\Notification\IManager as INotificationManager; -use OCP\Util; - -abstract class BackendUtility { - protected $access; - - /** - * constructor, make sure the subclasses call this one! - * @param Access $access an instance of Access for LDAP interaction - */ - public function __construct(Access $access) { - $this->access = $access; - } -} - - -class OC_USER_SQL extends BackendUtility implements \OCP\IUserBackend, - \OCP\UserInterface -{ - protected $cache; - protected $settings; - protected $helper; - protected $session_cache_name; - protected $ocConfig; - - /** - * The default constructor. It loads the settings for the given domain - * and tries to connect to the database. - */ - public function __construct() - { - $memcache = \OC::$server->getMemCacheFactory(); - if ( $memcache -> isAvailable()) - { - $this -> cache = $memcache -> create(); - } - $this -> helper = new \OCA\user_sql\lib\Helper(); - $domain = \OC::$server->getRequest()->getServerHost(); - $this -> settings = $this -> helper -> loadSettingsForDomain($domain); - $this -> ocConfig = \OC::$server->getConfig(); - $this -> helper -> connectToDb($this -> settings); - $this -> session_cache_name = 'USER_SQL_CACHE'; - return false; - } - - /** - * Sync the user's E-Mail address with the address stored by ownCloud. - * We have three (four) sync modes: - * - none: Does nothing - * - initial: Do the sync only once from SQL -> ownCloud - * - forcesql: The SQL database always wins and sync to ownCloud - * - forceoc: ownCloud always wins and syncs to SQL - * - * @param string $uid The user's ID to sync - * @return bool Success or Fail - */ - private function doEmailSync($uid) - { - Util::writeLog('OC_USER_SQL', "Entering doEmailSync for UID: $uid", - Util::DEBUG); - if($this -> settings['col_email'] === '') - return false; - - if($this -> settings['set_mail_sync_mode'] === 'none') - return false; - - $ocUid = $uid; - $uid = $this -> doUserDomainMapping($uid); - - $row = $this -> helper -> runQuery('getMail', array('uid' => $uid)); - if($row === false) - { - return false; - } - $newMail = $row[$this -> settings['col_email']]; - - $currMail = $this->ocConfig->getUserValue( $ocUid, - 'settings', - 'email', ''); - - switch($this -> settings['set_mail_sync_mode']) - { - case 'initial': - if($currMail === '') - $this->ocConfig->setUserValue( $ocUid, - 'settings', - 'email', - $newMail); - break; - case 'forcesql': - //if($currMail !== $newMail) - $this->ocConfig->setUserValue( $ocUid, - 'settings', - 'email', - $newMail); - break; - case 'forceoc': - if(($currMail !== '') && ($currMail !== $newMail)) - { - $row = $this -> helper -> runQuery('setMail', - array('uid' => $uid, - 'currMail' => $currMail) - , true); - - if($row === false) - { - Util::writeLog('OC_USER_SQL', - "Could not update E-Mail address in SQL database!", - Util::ERROR); - } - } - break; - } - - return true; - } - - /** - * This maps the username to the specified domain name. - * It can only append a default domain name. - * - * @param string $uid The UID to work with - * @return string The mapped UID - */ - private function doUserDomainMapping($uid) - { - $uid = trim($uid); - - if($this -> settings['set_default_domain'] !== '') - { - Util::writeLog('OC_USER_SQL', "Append default domain: ". - $this -> settings['set_default_domain'], Util::DEBUG); - if(strpos($uid, '@') === false) - { - $uid .= "@" . $this -> settings['set_default_domain']; - } - } - - $uid = strtolower($uid); - Util::writeLog('OC_USER_SQL', 'Returning mapped UID: ' . $uid, - Util::DEBUG); - return $uid; - } - - /** - * Return the actions implemented by this backend - * @param $actions - * @return bool - */ - public function implementsActions($actions) - { - return (bool)((Backend::CHECK_PASSWORD - | Backend::GET_DISPLAYNAME - | Backend::COUNT_USERS - | ($this -> settings['set_allow_pwchange'] === 'true' ? - Backend::SET_PASSWORD : 0) - | ($this -> settings['set_enable_gethome'] === 'true' ? - Backend::GET_HOME : 0) - ) & $actions); - } - - /** - * Checks if this backend has user listing support - * @return bool - */ - public function hasUserListings() - { - return true; - } - - /** - * Return the user's home directory, if enabled - * @param string $uid The user's ID to retrieve - * @return mixed The user's home directory or false - */ - public function getHome($uid) - { - Util::writeLog('OC_USER_SQL', "Entering getHome for UID: $uid", - Util::DEBUG); - - if($this -> settings['set_enable_gethome'] !== 'true') - return false; - - $uidMapped = $this -> doUserDomainMapping($uid); - $home = false; - - switch($this->settings['set_gethome_mode']) - { - case 'query': - Util::writeLog('OC_USER_SQL', - "getHome with Query selected, running Query...", - Util::DEBUG); - $row = $this -> helper -> runQuery('getHome', - array('uid' => $uidMapped)); - if($row === false) - { - Util::writeLog('OC_USER_SQL', - "Got no row, return false", - Util::DEBUG); - return false; - } - $home = $row[$this -> settings['col_gethome']]; - break; - - case 'static': - Util::writeLog('OC_USER_SQL', - "getHome with static selected", - Util::DEBUG); - $home = $this -> settings['set_gethome']; - $home = str_replace('%ud', $uidMapped, $home); - $home = str_replace('%u', $uid, $home); - $home = str_replace('%d', - $this -> settings['set_default_domain'], - $home); - break; - } - Util::writeLog('OC_USER_SQL', - "Returning getHome for UID: $uid with Home $home", - Util::DEBUG); - return $home; - } - - /** - * Create a new user account using this backend - * @return bool always false, as we can't create users - */ - public function createUser() - { - // Can't create user - Util::writeLog('OC_USER_SQL', - 'Not possible to create local users from web'. - ' frontend using SQL user backend', Util::ERROR); - return false; - } - - /** - * Delete a user account using this backend - * @param string $uid The user's ID to delete - * @return bool always false, as we can't delete users - */ - public function deleteUser($uid) - { - // Can't delete user - Util::writeLog('OC_USER_SQL', 'Not possible to delete local users'. - ' from web frontend using SQL user backend', Util::ERROR); - return false; - } - - /** - * Set (change) a user password - * This can be enabled/disabled in the settings (set_allow_pwchange) - * - * @param string $uid The user ID - * @param string $password The user's new password - * @return bool The return status - */ - public function setPassword($uid, $password) - { - // Update the user's password - this might affect other services, that - // use the same database, as well - Util::writeLog('OC_USER_SQL', "Entering setPassword for UID: $uid", - Util::DEBUG); - - if($this -> settings['set_allow_pwchange'] !== 'true') - return false; - - $uid = $this -> doUserDomainMapping($uid); - - $row = $this -> helper -> runQuery('getPass', array('uid' => $uid)); - if($row === false) - { - return false; - } - $old_password = $row[$this -> settings['col_password']]; - - // Added and disabled updating passwords for Drupal 7 WD 2018-01-04 - if($this -> settings['set_crypt_type'] === 'drupal') - { - return false; - } - elseif($this -> settings['set_crypt_type'] === 'joomla2') - { - if(!class_exists('\PasswordHash')) - require_once('PasswordHash.php'); - $hasher = new \PasswordHash(10, true); - $enc_password = $hasher -> HashPassword($password); - } - // Redmine stores the salt separatedly, this doesn't play nice with - // the way we check passwords - elseif($this -> settings['set_crypt_type'] === 'redmine') - { - $salt = $this -> helper -> runQuery('getRedmineSalt', - array('uid' => $uid)); - if(!$salt) - return false; - $enc_password = sha1($salt['salt'].sha1($password)); - - } - elseif($this -> settings['set_crypt_type'] === 'sha1') - { - $enc_password = sha1($password); - } - elseif($this -> settings['set_crypt_type'] === 'system') - { - $prefix=substr($old_password,0,2); - if ($prefix==="$2") - { - $enc_password = $this->pw_hash($password); - } - else - { - if (($prefix==="$1") or ($prefix[0] != "$")) //old md5 or DES - { - //Update encryption algorithm - $prefix="$6"; //change to sha512 - } - - $newsalt=$this->create_systemsalt(); - $enc_password=crypt($password,$prefix ."$" . $newsalt); - } - - } - elseif($this -> settings['set_crypt_type'] === 'password_hash') - { - $enc_password = $this->pw_hash($password); - } - elseif($this -> settings['set_crypt_type'] === 'courier_md5') - { - $enc_password = '{MD5}'.OC_USER_SQL::hex_to_base64(md5($password)); - } - elseif($this -> settings['set_crypt_type'] === 'courier_md5raw') - { - $enc_password = '{MD5RAW}'.md5($password); - } - elseif($this -> settings['set_crypt_type'] === 'courier_sha1') - { - $enc_password = '{SHA}'.OC_USER_SQL::hex_to_base64(sha1($password)); - } - elseif($this -> settings['set_crypt_type'] === 'courier_sha256') - { - $enc_password = '{SHA256}'.OC_USER_SQL::hex_to_base64(hash('sha256', $password, false)); - } - else - { - $enc_password = $this -> pacrypt($password, $old_password); - } - $res = $this -> helper -> runQuery('setPass', - array('uid' => $uid, 'enc_password' => $enc_password), - true); - if($res === false) - { - Util::writeLog('OC_USER_SQL', "Could not update password!", - Util::ERROR); - return false; - } - Util::writeLog('OC_USER_SQL', - "Updated password successfully, return true", - Util::DEBUG); - return true; - } - - /** - * Check if the password is correct - * @param string $uid The username - * @param string $password The password - * @return bool true/false - * - * Check if the password is correct without logging in the user - */ - public function checkPassword($uid, $password) - { - Util::writeLog('OC_USER_SQL', - "Entering checkPassword() for UID: $uid", - Util::DEBUG); - - $uid = $this -> doUserDomainMapping($uid); - - $row = $this -> helper -> runQuery('getPass', array('uid' => $uid)); - if($row === false) - { - Util::writeLog('OC_USER_SQL', "Got no row, return false", Util::DEBUG); - return false; - } - $db_pass = $row[$this -> settings['col_password']]; - - Util::writeLog('OC_USER_SQL', "Encrypting and checking password", - Util::DEBUG); - // Added handling for Drupal 7 passwords WD 2018-01-04 - if($this -> settings['set_crypt_type'] === 'drupal') - { - if(!function_exists('user_check_password')) - require_once('drupal.php'); - $ret = user_check_password($password, $db_pass); - } - // Joomla 2.5.18 switched to phPass, which doesn't play nice with the - // way we check passwords - elseif($this -> settings['set_crypt_type'] === 'joomla2') - { - if(!class_exists('\PasswordHash')) - require_once('PasswordHash.php'); - $hasher = new \PasswordHash(10, true); - $ret = $hasher -> CheckPassword($password, $db_pass); - } - elseif($this -> settings['set_crypt_type'] === 'password_hash') - { - $ret = password_verify($password,$db_pass); - } - // Redmine stores the salt separatedly, this doesn't play nice with the - // way we check passwords - elseif($this -> settings['set_crypt_type'] === 'redmine') - { - $salt = $this -> helper -> runQuery('getRedmineSalt', - array('uid' => $uid)); - if(!$salt) - return false; - $ret = sha1($salt['salt'].sha1($password)) === $db_pass; - } - - elseif($this -> settings['set_crypt_type'] == 'sha1') - { - $ret = $this->hash_equals(sha1($password) , $db_pass); - } - elseif($this -> settings['set_crypt_type'] === 'courier_md5') - { - $ret = '{MD5}'.OC_USER_SQL::hex_to_base64(md5($password)) === $db_pass; - } - elseif($this -> settings['set_crypt_type'] === 'courier_md5raw') - { - $ret = '{MD5RAW}'.md5($password) === $db_pass; - } - elseif($this -> settings['set_crypt_type'] === 'courier_sha1') - { - $ret = '{SHA}'.OC_USER_SQL::hex_to_base64(sha1($password)) === $db_pass; - } - elseif($this -> settings['set_crypt_type'] === 'courier_sha256') - { - $ret = '{SHA256}'.OC_USER_SQL::hex_to_base64(hash('sha256', $password, false)) === $db_pass; - } else - - { - // $ret = $this -> pacrypt($password, $db_pass) === $db_pass; - $ret = $this->hash_equals($this -> pacrypt($password, $db_pass), - $db_pass); - } - if($ret) - { - Util::writeLog('OC_USER_SQL', - "Passwords matching, return true", - Util::DEBUG); - if($this -> settings['set_strip_domain'] === 'true') - { - $uid = explode("@", $uid); - $uid = $uid[0]; - } - return $uid; - } else - { - Util::writeLog('OC_USER_SQL', - "Passwords do not match, return false", - Util::DEBUG); - return false; - } - } - - /** - * Count the number of users - * @return int The user count - */ - public function countUsers() - { - Util::writeLog('OC_USER_SQL', "Entering countUsers()", - Util::DEBUG); - - $search = "%".$this -> doUserDomainMapping(""); - $userCount = $this -> helper -> runQuery('countUsers', - array('search' => $search)); - if($userCount === false) - { - $userCount = 0; - } - else { - $userCount = reset($userCount); - } - - Util::writeLog('OC_USER_SQL', "Return usercount: ".$userCount, - Util::DEBUG); - return $userCount; - } - - /** - * Get a list of all users - * @param string $search The search term (can be empty) - * @param int $limit The search limit (can be null) - * @param int $offset The search offset (can be null) - * @return array with all uids - */ - public function getUsers($search = '', $limit = null, $offset = null) - { - Util::writeLog('OC_USER_SQL', - "Entering getUsers() with Search: $search, ". - "Limit: $limit, Offset: $offset", Util::DEBUG); - $users = array(); - - if($search !== '') - { - $search = "%".$this -> doUserDomainMapping($search."%")."%"; - } - else - { - $search = "%".$this -> doUserDomainMapping("")."%"; - } - - $rows = $this -> helper -> runQuery('getUsers', - array('search' => $search), - false, - true, - array('limit' => $limit, - 'offset' => $offset)); - if($rows === false) - return array(); - - foreach($rows as $row) - { - $uid = $row[$this -> settings['col_username']]; - if($this -> settings['set_strip_domain'] === 'true') - { - $uid = explode("@", $uid); - $uid = $uid[0]; - } - $users[] = strtolower($uid); - } - Util::writeLog('OC_USER_SQL', "Return list of results", - Util::DEBUG); - return $users; - } - - /** - * Check if a user exists - * @param string $uid the username - * @return boolean - */ - public function userExists($uid) - { - - $cacheKey = 'sql_user_exists_' . $uid; - $cacheVal = $this -> getCache ($cacheKey); - Util::writeLog('OC_USER_SQL', - "userExists() for UID: $uid cacheVal: $cacheVal", - Util::DEBUG); - if(!is_null($cacheVal)) - return (bool)$cacheVal; - - Util::writeLog('OC_USER_SQL', - "Entering userExists() for UID: $uid", - Util::DEBUG); - - // Only if the domain is removed for internal user handling, - // we should add the domain back when checking existance - if($this -> settings['set_strip_domain'] === 'true') - { - $uid = $this -> doUserDomainMapping($uid); - } - - $exists = (bool)$this -> helper -> runQuery('userExists', - array('uid' => $uid));; - $this -> setCache ($cacheKey, $exists, 60); - - if(!$exists) - { - Util::writeLog('OC_USER_SQL', - "Empty row, user does not exists, return false", - Util::DEBUG); - return false; - } else - { - Util::writeLog('OC_USER_SQL', "User exists, return true", - Util::DEBUG); - return true; - } - - } - - /** - * Get the display name of the user - * @param string $uid The user ID - * @return mixed The user's display name or FALSE - */ - public function getDisplayName($uid) - { - Util::writeLog('OC_USER_SQL', - "Entering getDisplayName() for UID: $uid", - Util::DEBUG); - - $this -> doEmailSync($uid); - $uid = $this -> doUserDomainMapping($uid); - - if(!$this -> userExists($uid)) - { - return false; - } - - $row = $this -> helper -> runQuery('getDisplayName', - array('uid' => $uid)); - - if(!$row) - { - Util::writeLog('OC_USER_SQL', - "Empty row, user has no display name or ". - "does not exist, return false", - Util::DEBUG); - return false; - } else - { - Util::writeLog('OC_USER_SQL', - "User exists, return true", - Util::DEBUG); - $displayName = $row[$this -> settings['col_displayname']]; - return $displayName; ; - } - return false; - } - - public function getDisplayNames($search = '', $limit = null, $offset = null) - { - $uids = $this -> getUsers($search, $limit, $offset); - $displayNames = array(); - foreach($uids as $uid) - { - $displayNames[$uid] = $this -> getDisplayName($uid); - } - return $displayNames; - } - - /** - * Returns the backend name - * @return string - */ - public function getBackendName() - { - return 'SQL'; - } - - /** - * The following functions were directly taken from PostfixAdmin and just - * slightly modified - * to suit our needs. - * Encrypt a password,using the apparopriate hashing mechanism as defined in - * config.inc.php ($this->crypt_type). - * When wanting to compare one pw to another, it's necessary to provide the - * salt used - hence - * the second parameter ($pw_db), which is the existing hash from the DB. - * - * @param string $pw cleartext password - * @param string $pw_db encrypted password from database - * @return string encrypted password. - */ - private function pacrypt($pw, $pw_db = "") - { - Util::writeLog('OC_USER_SQL', "Entering private pacrypt()", - Util::DEBUG); - $pw = stripslashes($pw); - $password = ""; - $salt = ""; - - if($this -> settings['set_crypt_type'] === 'md5crypt') - { - $split_salt = preg_split('/\$/', $pw_db); - if(isset($split_salt[2])) - { - $salt = $split_salt[2]; - } - $password = $this -> md5crypt($pw, $salt); - } elseif($this -> settings['set_crypt_type'] === 'md5') - { - $password = md5($pw); - } elseif($this -> settings['set_crypt_type'] === 'system') - { - // We never generate salts, as user creation is not allowed here - $password = crypt($pw, $pw_db); - } elseif($this -> settings['set_crypt_type'] === 'cleartext') - { - $password = $pw; - } - -// See -// https://sourceforge.net/tracker/?func=detail&atid=937966&aid=1793352&group_id=191583 -// this is apparently useful for pam_mysql etc. - elseif($this -> settings['set_crypt_type'] === 'mysql_encrypt') - { - if($pw_db !== "") - { - $salt = substr($pw_db, 0, 2); - - $row = $this -> helper -> runQuery('mysqlEncryptSalt', - array('pw' => $pw, 'salt' => $salt)); - } else - { - $row = $this -> helper -> runQuery('mysqlEncrypt', - array('pw' => $pw)); - } - - if($row === false) - { - return false; - } - $password = $row[0]; - } elseif($this -> settings['set_crypt_type'] === 'mysql_password') - { - $row = $this -> helper -> runQuery('mysqlPassword', - array('pw' => $pw)); - - if($row === false) - { - return false; - } - $password = $row[0]; - } - - // The following is by Frédéric France - elseif($this -> settings['set_crypt_type'] === 'joomla') - { - $split_salt = preg_split('/:/', $pw_db); - if(isset($split_salt[1])) - { - $salt = $split_salt[1]; - } - $password = ($salt) ? md5($pw . $salt) : md5($pw); - $password .= ':' . $salt; - } - - elseif($this-> settings['set_crypt_type'] === 'ssha256') - { - $salted_password = base64_decode( - preg_replace('/{SSHA256}/i','',$pw_db)); - $salt = substr($salted_password,-(strlen($salted_password)-32)); - $password = $this->ssha256($pw,$salt); - } else - { - Util::writeLog('OC_USER_SQL', - "unknown/invalid crypt_type settings: ". - $this->settings['set_crypt_type'], - Util::ERROR); - die('unknown/invalid Encryption type setting: ' . - $this -> settings['set_crypt_type']); - } - Util::writeLog('OC_USER_SQL', "pacrypt() done, return", - Util::DEBUG); - return $password; - } - - /** - * md5crypt - * Creates MD5 encrypted password - * @param string $pw The password to encrypt - * @param string $salt The salt to use - * @param string $magic ? - * @return string The encrypted password - */ - - private function md5crypt($pw, $salt = "", $magic = "") - { - $MAGIC = "$1$"; - - if($magic === "") - $magic = $MAGIC; - if($salt === "") - $salt = $this -> create_md5salt(); - $slist = explode("$", $salt); - if($slist[0] === "1") - $salt = $slist[1]; - - $salt = substr($salt, 0, 8); - $ctx = $pw . $magic . $salt; - $final = $this -> pahex2bin(md5($pw . $salt . $pw)); - - for($i = strlen($pw); $i > 0; $i -= 16) - { - if($i > 16) - { - $ctx .= substr($final, 0, 16); - } else - { - $ctx .= substr($final, 0, $i); - } - } - $i = strlen($pw); - - while($i > 0) - { - if($i & 1) - $ctx .= chr(0); - else - $ctx .= $pw[0]; - $i = $i>>1; - } - $final = $this -> pahex2bin(md5($ctx)); - - for($i = 0; $i < 1000; $i++) - { - $ctx1 = ""; - if($i & 1) - { - $ctx1 .= $pw; - } else - { - $ctx1 .= substr($final, 0, 16); - } - if($i % 3) - $ctx1 .= $salt; - if($i % 7) - $ctx1 .= $pw; - if($i & 1) - { - $ctx1 .= substr($final, 0, 16); - } else - { - $ctx1 .= $pw; - } - $final = $this -> pahex2bin(md5($ctx1)); - } - $passwd = ""; - $passwd .= $this -> to64(((ord($final[0])<<16) | - (ord($final[6])<<8) | (ord($final[12]))), 4); - $passwd .= $this -> to64(((ord($final[1])<<16) | - (ord($final[7])<<8) | (ord($final[13]))), 4); - $passwd .= $this -> to64(((ord($final[2])<<16) | - (ord($final[8])<<8) | (ord($final[14]))), 4); - $passwd .= $this -> to64(((ord($final[3])<<16) | - (ord($final[9])<<8) | (ord($final[15]))), 4); - $passwd .= $this -> to64(((ord($final[4])<<16) | - (ord($final[10])<<8) | (ord($final[5]))), 4); - $passwd .= $this -> to64(ord($final[11]), 2); - return "$magic$salt\$$passwd"; - } - - /** - * Create a new salte - * @return string The salt - */ - private function create_md5salt() - { - srand((double) microtime() * 1000000); - $salt = substr(md5(rand(0, 9999999)), 0, 8); - return $salt; - } - - /** - * Encrypt using SSHA256 algorithm - * @param string $pw The password - * @param string $salt The salt to use - * @return string The hashed password, prefixed by {SSHA256} - */ - private function ssha256($pw, $salt) - { - return '{SSHA256}'.base64_encode(hash('sha256',$pw.$salt,true).$salt); - } - - /** - * PostfixAdmin's hex2bin function - * @param string $str The string to convert - * @return string The converted string - */ - private function pahex2bin($str) - { - if(function_exists('hex2bin')) - { - return hex2bin($str); - } else - { - $len = strlen($str); - $nstr = ""; - for($i = 0; $i < $len; $i += 2) - { - $num = sscanf(substr($str, $i, 2), "%x"); - $nstr .= chr($num[0]); - } - return $nstr; - } - } - - /** - * Convert to 64? - */ - private function to64($v, $n) - { - $ITOA64 = - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - $ret = ""; - while(($n - 1) >= 0) - { - $n--; - $ret .= $ITOA64[$v & 0x3f]; - $v = $v>>6; - } - return $ret; - } - - /** - * Store a value in memcache or the session, if no memcache is available - * @param string $key The key - * @param mixed $value The value to store - * @param int $ttl (optional) defaults to 3600 seconds. - */ - private function setCache($key, $value, $ttl=3600) - { - if ($this -> cache === NULL) - { - $_SESSION[$this -> session_cache_name][$key] = array( - 'value' => $value, - 'time' => time(), - 'ttl' => $ttl, - ); - } else - { - $this -> cache -> set($key,$value,$ttl); - } - } - - /** - * Fetch a value from memcache or session, if memcache is not available. - * Returns NULL if there's no value stored or the value expired. - * @param string $key - * @return mixed|NULL - */ - private function getCache($key) - { - $retVal = NULL; - if ($this -> cache === NULL) - { - if (isset($_SESSION[$this -> session_cache_name], - $_SESSION[$this -> session_cache_name][$key])) - { - $value = $_SESSION[$this -> session_cache_name][$key]; - if (time() < $value['time'] + $value['ttl']) - { - $retVal = $value['value']; - } - } - } else - { - $retVal = $this -> cache -> get ($key); - } - return $retVal; - } - - private function create_systemsalt($length=20) - { - $fp = fopen('/dev/urandom', 'r'); - $randomString = fread($fp, $length); - fclose($fp); - $salt = base64_encode($randomString); - return $salt; - } - - private function pw_hash($password) - { - $options = [ - 'cost' => 10, - ]; - return password_hash($password, PASSWORD_BCRYPT, $options); - - } - - function hash_equals( $a, $b ) { - $a_length = strlen( $a ); - - if ( $a_length !== strlen( $b ) ) { - return false; - } - $result = 0; - - // Do not attempt to "optimize" this. - for ( $i = 0; $i < $a_length; $i++ ) { - $result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] ); - } - - //Hide the length of the string - $additional_length=200-($a_length % 200); - $tmp=0; - $c="abCD"; - for ( $i = 0; $i < $additional_length; $i++ ) { - $tmp |= ord( $c[ 0 ] ) ^ ord( $c[ 0 ] ); - } - - return $result === 0; - } - - private static function hex_to_base64($hex) - { - $hex_chr = ''; - foreach(str_split($hex, 2) as $hexpair) - { - $hex_chr .= chr(hexdec($hexpair)); - } - return base64_encode($hex_chr); - } - -} diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index fd2ed67..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/templates/admin.php b/templates/admin.php index 4820559..c0bc1ba 100644 --- a/templates/admin.php +++ b/templates/admin.php @@ -1,181 +1,182 @@ + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use OCP\IL10N; + script('user_sql', 'settings'); style('user_sql', 'settings'); -$cfgClass = 'section'; -?> - -
-

t('SQL User Backend')); ?>

- -
- -
- - - - -
-

- 'MySQL', 'pgsql' => 'PostgreSQL'); ?> - -

- -

- -

- -

- -

- -

- -
-
-

- -

- -

- -

>
- t('Allow changing passwords. Imposes a security risk if password salts are not recreated.')); ?>

- t('Only the encryption types "System","password_hash" and "Joomla2" are safe.')); ?>

- -

- -

- 'Drupal 7', 'md5' => 'MD5', 'md5crypt' => 'MD5 Crypt', 'cleartext' => 'Cleartext', 'mysql_encrypt' => 'mySQL ENCRYPT()', 'system' => 'System (crypt)', 'password_hash' => 'password_hash','mysql_password' => 'mySQL PASSWORD()', 'joomla' => 'Joomla MD5 Encryption', 'joomla2' => 'Joomla > 2.5.18 phpass', 'ssha256' => 'Salted SSHA256', 'redmine' => 'Redmine', 'sha1' => 'SHA1', 'courier_md5' => 'Courier base64-encoded MD5', 'courier_md5raw' => 'Courier hexadecimal MD5', 'courier_sha1' => 'Courier base64-encoded SHA1', 'courier_sha256' => 'Courier base64-encoded SHA256'); ?> - -

- -

- -

/>
- t("Invert the logic of the active column (for blocked users in the SQL DB)")); ?>

- -
-
+function print_text_input(IL10N $l, $id, $label, $value = "", $type = "text") +{ + echo "
"; +} + +function print_checkbox_input(IL10N $l, $id, $label, $value = "", $div = true) +{ + if ($div) { + echo "
"; + } + echo ""; + echo ""; + if ($div) { + echo "
"; + } +} + +function print_select_options( + IL10N $l, $id, $label, $options = [], $select = false +) { + echo "
"; +} -

- -

- 'No Synchronisation', 'initial' => 'Synchronise only once', 'forceoc' => 'Nextcloud always wins', 'forcesql' => 'SQL always wins'); ?> - -

- -
- -
- -


- t('Append this string, e.g. a domain name, to each user name. The @-sign is automatically inserted.')); ?> -

- -

/>
- t("Strip Domain Part including @-sign from Username when logging in and retrieving username lists")); ?>

- -
- -
-

/>

- -

- 'SQL Column', 'static' => 'Static (with Variables)'); ?> - -

- -

- -


- t('You can use the placeholders %%u to specify the user ID (before appending the default domain), %%ud to specify the user ID (after appending the default domain) and %%d to specify the default domain')); ?>

- -
-
-

- -

- -

- -
- - - - -
t('Saving...')); ?>
-
t('Loading...')); ?>
-
t('Verifying...')); ?>
-
-
+?> + + +
+

+ t("This is the place for ")); ?> + t("User and Group SQL Backends")); ?> + t(" app settings. Please see the documentation for more information.")); ?> + +

+
+
+
+

t("Database connection")); ?>

+

t("Define your database connection parameters.")); ?>

+
"MySQL", "pgsql" => "PostgreSQL"]; + print_select_options($l, "db-driver", "SQL driver", $drivers, $_['db.driver']); + print_text_input($l, "db-hostname", "Hostname", $_['db.hostname']); + print_text_input($l, "db-database", "Database", $_['db.database']); + print_text_input($l, "db-username", "Username", $_['db.username']); + print_text_input($l, "db-password", "Password", $_['db.password'], "password"); ?> +
+ "> +
+
+
+
+

t("Options")); ?>

+

t("Here are all currently supported options.")); ?>

+
+
+ "> +
+ getVisibleName(); + } + } catch (Throwable $e) { + } + } + + print_select_options($l, "opt-crypto_class", "Hashing algorithm", $hashing, $_['opt.crypto_class']); + print_select_options($l, "opt-email_sync", "Email sync", ["" => "None", "initial" => "Synchronise only once", "force_nc"=>"Nextcloud always wins", "force_sql"=>"SQL always wins"], $_['opt.email_sync']); + print_select_options($l, "opt-home_mode", "Home mode", ["" => "Default", "query" => "Query", "static" => "Static"], $_['opt.home_mode']); + print_text_input($l, "opt-home_location", "Home Location", $_['opt.home_location']); ?> +
+
+
+

t("User table")); ?>

+

t("Table containing user accounts.")); ?>

+
+

t("Columns")); ?>

+ +
+
+
+

t("Group table")); ?>

+

t("Group definitions table.")); ?>

+
+

t("Columns")); ?>

+ +
+
+
+

t("User group table")); ?>

+

t("Associative table which maps users to groups.")); ?>

+
+

t("Columns")); ?>

+ +
+
+
+
+ + +
- -
+ \ No newline at end of file diff --git a/tests/Crypto/CleartextTest.php b/tests/Crypto/CleartextTest.php new file mode 100644 index 0000000..d4eaaef --- /dev/null +++ b/tests/Crypto/CleartextTest.php @@ -0,0 +1,51 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\Cleartext; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class Cleartext. + * + * @author Marcin Łojewski + */ +class CleartextTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue($this->crypto->checkPassword("password", "password")); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new Cleartext($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CourierMD5RawTest.php b/tests/Crypto/CourierMD5RawTest.php new file mode 100644 index 0000000..fe30008 --- /dev/null +++ b/tests/Crypto/CourierMD5RawTest.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CourierMD5Raw; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CourierMD5Raw. + * + * @author Marcin Łojewski + */ +class CourierMD5RawTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "{MD5RAW}5f4dcc3b5aa765d61d8327deb882cf99" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CourierMD5Raw($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CourierMD5Test.php b/tests/Crypto/CourierMD5Test.php new file mode 100644 index 0000000..0d1e82d --- /dev/null +++ b/tests/Crypto/CourierMD5Test.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CourierMD5; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CourierMD5. + * + * @author Marcin Łojewski + */ +class CourierMD5Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "{MD5}X03MO1qnZdYdgyfeuILPmQ==" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CourierMD5($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CourierSHA1Test.php b/tests/Crypto/CourierSHA1Test.php new file mode 100644 index 0000000..0621655 --- /dev/null +++ b/tests/Crypto/CourierSHA1Test.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CourierSHA1; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CourierSHA1. + * + * @author Marcin Łojewski + */ +class CourierSHA1Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CourierSHA1($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CourierSHA256Test.php b/tests/Crypto/CourierSHA256Test.php new file mode 100644 index 0000000..ee86310 --- /dev/null +++ b/tests/Crypto/CourierSHA256Test.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CourierSHA256; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CourierSHA256. + * + * @author Marcin Łojewski + */ +class CourierSHA256Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CourierSHA256($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptArgon2Test.php b/tests/Crypto/CryptArgon2Test.php new file mode 100644 index 0000000..7a44ddd --- /dev/null +++ b/tests/Crypto/CryptArgon2Test.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptArgon2; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptArgon2. + * + * @author Marcin Łojewski + */ +class CryptArgon2Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "\$argon2i\$v=19\$m=1024,t=2,p=2\$NnpSNlRNLlZobnJHUDh0Sw\$oW5E1cfdPzLWfkTvQFUyzTR00R0aLwEdYwldcqW6Pmo" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptArgon2($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptBlowfishTest.php b/tests/Crypto/CryptBlowfishTest.php new file mode 100644 index 0000000..ea4dc0c --- /dev/null +++ b/tests/Crypto/CryptBlowfishTest.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptBlowfish; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptBlowfish. + * + * @author Marcin Łojewski + */ +class CryptBlowfishTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "$2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptBlowfish($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptExtendedDESTest.php b/tests/Crypto/CryptExtendedDESTest.php new file mode 100644 index 0000000..31ca7c1 --- /dev/null +++ b/tests/Crypto/CryptExtendedDESTest.php @@ -0,0 +1,53 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptExtendedDES; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptExtendedDES. + * + * @author Marcin Łojewski + */ +class CryptExtendedDESTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword("password", "..UZoIyj/Hy/c") + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptExtendedDES($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptMD5Test.php b/tests/Crypto/CryptMD5Test.php new file mode 100644 index 0000000..0a6f405 --- /dev/null +++ b/tests/Crypto/CryptMD5Test.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptMD5; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptMD5. + * + * @author Marcin Łojewski + */ +class CryptMD5Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "$1\$RzaFbNcU\$u9adfTY/Q6za6nu0Ogrl1/" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptMD5($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptSHA256Test.php b/tests/Crypto/CryptSHA256Test.php new file mode 100644 index 0000000..020bb61 --- /dev/null +++ b/tests/Crypto/CryptSHA256Test.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptSHA256; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptSHA256. + * + * @author Marcin Łojewski + */ +class CryptSHA256Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "$5\$rounds=5000\$VIYD0iHkg7uY9SRc\$v2XLS/9dvfFN84mzGvW9wxnVt9Xd/urXaaTkpW8EwD1" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptSHA256($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptSHA512Test.php b/tests/Crypto/CryptSHA512Test.php new file mode 100644 index 0000000..7667d1f --- /dev/null +++ b/tests/Crypto/CryptSHA512Test.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptSHA512; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptSHA512. + * + * @author Marcin Łojewski + */ +class CryptSHA512Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "$6\$rounds=5000\$yH.Q0OL4qbCOUJ3q\$Xry5EVFva3wKnfo8/ktrugmBd8tcl34NK6rXInv1HhmdSUNLEm0La9JnA57rqwQ.9/Bz513MD4tvmmISLUIHs/" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptSHA512($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptStandardDESTest.php b/tests/Crypto/CryptStandardDESTest.php new file mode 100644 index 0000000..ca8712b --- /dev/null +++ b/tests/Crypto/CryptStandardDESTest.php @@ -0,0 +1,53 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\CryptStandardDES; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class CryptStandardDES. + * + * @author Marcin Łojewski + */ +class CryptStandardDESTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword("password", "yTBnb7ab/N072") + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new CryptStandardDES($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/CryptTest.php b/tests/Crypto/CryptTest.php new file mode 100644 index 0000000..f556289 --- /dev/null +++ b/tests/Crypto/CryptTest.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\Crypt; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class Crypt. + * + * @author Marcin Łojewski + */ +class CryptTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "$2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new Crypt($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/JoomlaTest.php b/tests/Crypto/JoomlaTest.php new file mode 100644 index 0000000..feaa96e --- /dev/null +++ b/tests/Crypto/JoomlaTest.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCA\UserSQL\Crypto\Joomla; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class Joomla. + * + * @author Marcin Łojewski + */ +class JoomlaTest extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "14d21b49b0f13e2acba962b6b0039edd:haJK0yTvBXTNMh76xwEw5RYEVpJsN8us" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new Joomla($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/MD5Test.php b/tests/Crypto/MD5Test.php new file mode 100644 index 0000000..d8f2950 --- /dev/null +++ b/tests/Crypto/MD5Test.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\MD5; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class MD5. + * + * @author Marcin Łojewski + */ +class MD5Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "5f4dcc3b5aa765d61d8327deb882cf99" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new MD5($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/SHA1Test.php b/tests/Crypto/SHA1Test.php new file mode 100644 index 0000000..2ed51ab --- /dev/null +++ b/tests/Crypto/SHA1Test.php @@ -0,0 +1,55 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\SHA1; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class SHA1. + * + * @author Marcin Łojewski + */ +class SHA1Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new SHA1($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/SSHA256Test.php b/tests/Crypto/SSHA256Test.php new file mode 100644 index 0000000..f26b0e7 --- /dev/null +++ b/tests/Crypto/SSHA256Test.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\SSHA256; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class SSHA256. + * + * @author Marcin Łojewski + */ +class SSHA256Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "{SSHA256}+WxTB3JxprNteeovsuSYtgI+UkVPA9lfwGoYkz3Ff7hjd1FSdmlTMkNsSExyR21KM3NvNTZ5V0p4WXJMUjFzUg==" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new SSHA256($this->createMock(IL10N::class)); + } +} diff --git a/tests/Crypto/SSHA512Test.php b/tests/Crypto/SSHA512Test.php new file mode 100644 index 0000000..10cfbd7 --- /dev/null +++ b/tests/Crypto/SSHA512Test.php @@ -0,0 +1,56 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\SSHA512; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class SSHA512. + * + * @author Marcin Łojewski + */ +class SSHA512Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", + "{SSHA512}It+v1kAEUBbhMJYJ2swAtz+RLE6ispv/FB6G/ALhK/YWwEmrloY+0jzrWIfmu+rWUXp8u0Tg4jLXypC5oXAW00IyYnRVdEZJbE9wak96bkNRVWFCYmlJNWxrdTA0QmhL" + ) + ); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new SSHA512($this->createMock(IL10N::class)); + } +}