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')); ?>
-
-
\ 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));
+ }
+}