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/ajax/settings.php b/ajax/settings.php
deleted file mode 100644
index ed0f406..0000000
--- a/ajax/settings.php
+++ /dev/null
@@ -1,276 +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 Nextcloud
-
-// 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 b984edd..716393f 100644
--- a/appinfo/app.php
+++ b/appinfo/app.php
@@ -1,31 +1,31 @@
+ * @copyright 2018 Marcin Łojewski
+ * @author Marcin Łojewski
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 library. If not, see .
+ * 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';
+use OCA\UserSQL\AppInfo\Application;
+use OCP\AppFramework\QueryException;
-$backend = new \OCA\user_sql\OC_USER_SQL;
-$group_backend = new \OCA\user_sql\OC_GROUP_SQL;
-
-\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 fa70152..67aa41e 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -1,28 +1,31 @@
user_sql
- SQL User Backend
- Authenticate Users by SQL
- Authenticate users and retrieve their groups from external database by native SQL queries.
+ 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-devagpl
- Andreas Boehler <dev (at) aboehler (dot) at >
- user_sql
+ Andreas Böhler <dev (at) aboehler (dot) at>
+ Marcin Łojewski <dev (at) mlojewski (dot) me>
+ UserSQLhttps://github.com/nextcloud/user_sql/issueshttps://github.com/nextcloud/user_sql
- https://raw.githubusercontent.com/nextcloud/user_sql/v4.0.0/img/screenshot.png
+ https://raw.githubusercontent.com/nextcloud/user_sql/master/img/screenshot.pngauth
-
-
- mysql
- pgsql
+
+
- \OCA\user_sql\Settings\Admin
- OCA\user_sql\Settings\Section
+ \OCA\UserSQL\Settings\Admin
+ OCA\UserSQL\Settings\Section
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 1617c63..8224fa4 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -1,9 +1,66 @@
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
+ * 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 .
*/
-/** @var $this \OCP\Route\IRouter */
-$this->create('user_sql_ajax_settings', 'ajax/settings.php')->actionInclude('user_sql/ajax/settings.php');
+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/img/app-dark.svg b/img/app-dark.svg
index 24168c8..f6c7cf3 100644
--- a/img/app-dark.svg
+++ b/img/app-dark.svg
@@ -1,5 +1 @@
-
-
+
\ No newline at end of file
diff --git a/lib/Action/EmailSync.php b/lib/Action/EmailSync.php
new file mode 100644
index 0000000..c027faf
--- /dev/null
+++ b/lib/Action/EmailSync.php
@@ -0,0 +1,136 @@
+
+ * @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
+ */
+ 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..3cec6bb
--- /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..0552b04
--- /dev/null
+++ b/lib/Backend/UserBackend.php
@@ -0,0 +1,566 @@
+
+ * @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_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/HashAlgorithm/Base/Singleton.php b/lib/Constant/App.php
similarity index 59%
rename from lib/HashAlgorithm/Base/Singleton.php
rename to lib/Constant/App.php
index 3dc4440..a2973ce 100644
--- a/lib/HashAlgorithm/Base/Singleton.php
+++ b/lib/Constant/App.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,33 +19,22 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm\Base;
+namespace OCA\UserSQL\Constant;
/**
- * Singleton pattern trait.
+ * The application constants.
+ *
* @author Marcin Łojewski
*/
-trait Singleton
+final class App
{
- private static $instance;
+ const FALSE_VALUE = "0";
+ const TRUE_VALUE = "1";
- final private function __construct()
- {
- $this->init();
- }
+ const HOME_QUERY = "query";
+ const HOME_STATIC = "static";
- protected function init()
- {
- }
-
- final public static function getInstance()
- {
- return isset(static::$instance)
- ? static::$instance
- : static::$instance = new static;
- }
-
- final private function __clone()
- {
- }
+ 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..c8bf37f
--- /dev/null
+++ b/lib/Controller/SettingsController.php
@@ -0,0 +1,350 @@
+
+ * @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();
+
+ 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/HashAlgorithm/Base/BaseCrypt.php b/lib/Crypto/AbstractCrypt.php
similarity index 70%
rename from lib/HashAlgorithm/Base/BaseCrypt.php
rename to lib/Crypto/AbstractCrypt.php
index 620cbea..9d053ec 100644
--- a/lib/HashAlgorithm/Base/BaseCrypt.php
+++ b/lib/Crypto/AbstractCrypt.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,24 +19,21 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm\Base;
+namespace OCA\UserSQL\Crypto;
/**
- * Implements standard Unix DES-based algorithm or
- * alternative algorithms that may be available on the system.
- * @see crypt()
+ * Abstract Unix Crypt hashing implementation.
+ * The hashing algorithm depends on the chosen salt.
+ *
+ * @see crypt()
* @author Marcin Łojewski
*/
-abstract class BaseCrypt implements HashAlgorithm
+abstract class AbstractCrypt extends AbstractAlgorithm
{
- use Singleton;
-
- const SALT_ALPHABET = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
/**
- * @inheritdoc
+ * The chars used in the salt.
*/
- abstract public function getVisibleName();
+ const SALT_ALPHABET = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* @inheritdoc
@@ -53,8 +52,12 @@ abstract class BaseCrypt implements HashAlgorithm
}
/**
- * Generate salt for hashing algorithm.
- * @return string
+ * Generate a salt string for the hashing algorithm.
+ *
+ * @return string The salt string.
*/
- protected abstract function getSalt();
+ protected function getSalt()
+ {
+ return "";
+ }
}
diff --git a/lib/HashAlgorithm/Cleartext.php b/lib/Crypto/Cleartext.php
similarity index 70%
rename from lib/HashAlgorithm/Cleartext.php
rename to lib/Crypto/Cleartext.php
index c15b11b..e33d919 100644
--- a/lib/HashAlgorithm/Cleartext.php
+++ b/lib/Crypto/Cleartext.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,25 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
+use OCP\IL10N;
/**
* Cleartext password implementation.
+ *
* @author Marcin Łojewski
*/
-class Cleartext implements HashAlgorithm
+class Cleartext extends AbstractAlgorithm
{
- use Singleton;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Cleartext";
+ parent::__construct($localization);
}
/**
@@ -49,8 +51,8 @@ class Cleartext implements HashAlgorithm
/**
* @inheritdoc
*/
- public function checkPassword($password, $dbHash)
+ protected function getAlgorithmName()
{
- return hash_equals($dbHash, $password);
+ return "Cleartext";
}
}
diff --git a/lib/HashAlgorithm/CourierMD5.php b/lib/Crypto/CourierMD5.php
similarity index 65%
rename from lib/HashAlgorithm/CourierMD5.php
rename to lib/Crypto/CourierMD5.php
index 00cd67a..6e8e71f 100644
--- a/lib/HashAlgorithm/CourierMD5.php
+++ b/lib/Crypto/CourierMD5.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,35 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* Courier MD5 hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class CourierMD5 implements HashAlgorithm
+class CourierMD5 extends AbstractAlgorithm
{
- use Singleton;
- use Utils;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Courier base64-encoded MD5";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, $this->getPasswordHash($password));
+ parent::__construct($localization);
}
/**
@@ -53,6 +45,14 @@ class CourierMD5 implements HashAlgorithm
*/
public function getPasswordHash($password)
{
- return '{MD5}' . self::hexToBase64(md5($password));
+ return '{MD5}' . Utils::hexToBase64(md5($password));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "Courier base64-encoded MD5";
}
}
diff --git a/lib/HashAlgorithm/CourierMD5Raw.php b/lib/Crypto/CourierMD5Raw.php
similarity index 70%
rename from lib/HashAlgorithm/CourierMD5Raw.php
rename to lib/Crypto/CourierMD5Raw.php
index d0cc952..39fd3db 100644
--- a/lib/HashAlgorithm/CourierMD5Raw.php
+++ b/lib/Crypto/CourierMD5Raw.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,33 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
+use OCP\IL10N;
/**
* Courier MD5 RAW hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class CourierMD5Raw implements HashAlgorithm
+class CourierMD5Raw extends AbstractAlgorithm
{
- use Singleton;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Courier hexadecimal MD5";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, $this->getPasswordHash($password));
+ parent::__construct($localization);
}
/**
@@ -53,4 +47,12 @@ class CourierMD5Raw implements HashAlgorithm
{
return '{MD5RAW}' . md5($password);
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "Courier hexadecimal MD5";
+ }
}
diff --git a/lib/HashAlgorithm/CourierSHA1.php b/lib/Crypto/CourierSHA1.php
similarity index 64%
rename from lib/HashAlgorithm/CourierSHA1.php
rename to lib/Crypto/CourierSHA1.php
index 5c2a82b..15d2ef3 100644
--- a/lib/HashAlgorithm/CourierSHA1.php
+++ b/lib/Crypto/CourierSHA1.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,35 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* Courier SHA1 hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class CourierSHA1 implements HashAlgorithm
+class CourierSHA1 extends AbstractAlgorithm
{
- use Singleton;
- use Utils;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Courier base64-encoded SHA1";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, $this->getPasswordHash($password));
+ parent::__construct($localization);
}
/**
@@ -53,6 +45,14 @@ class CourierSHA1 implements HashAlgorithm
*/
public function getPasswordHash($password)
{
- return '{SHA}' . self::hexToBase64(sha1($password));
+ return '{SHA}' . Utils::hexToBase64(sha1($password));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "Courier base64-encoded SHA1";
}
}
diff --git a/lib/HashAlgorithm/CourierSHA256.php b/lib/Crypto/CourierSHA256.php
similarity index 64%
rename from lib/HashAlgorithm/CourierSHA256.php
rename to lib/Crypto/CourierSHA256.php
index f275bbe..3bf0ed6 100644
--- a/lib/HashAlgorithm/CourierSHA256.php
+++ b/lib/Crypto/CourierSHA256.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,35 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* Courier SHA256 hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class CourierSHA256 implements HashAlgorithm
+class CourierSHA256 extends AbstractAlgorithm
{
- use Singleton;
- use Utils;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Courier base64-encoded SHA256";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, $this->getPasswordHash($password));
+ parent::__construct($localization);
}
/**
@@ -53,6 +45,14 @@ class CourierSHA256 implements HashAlgorithm
*/
public function getPasswordHash($password)
{
- return '{SHA256}' . self::hexToBase64(hash('sha256', $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..736b68d
--- /dev/null
+++ b/lib/Crypto/CryptArgon2.php
@@ -0,0 +1,97 @@
+
+ * @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
+ ) {
+ 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/HashAlgorithm/CryptBlowfish.php b/lib/Crypto/CryptBlowfish.php
similarity index 54%
rename from lib/HashAlgorithm/CryptBlowfish.php
rename to lib/Crypto/CryptBlowfish.php
index 2a2f1c0..8e4a35e 100644
--- a/lib/HashAlgorithm/CryptBlowfish.php
+++ b/lib/Crypto/CryptBlowfish.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,25 +19,34 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
+use OCP\IL10N;
/**
* Blowfish Crypt hashing implementation.
+ *
+ * @see crypt()
* @author Marcin Łojewski
*/
-class CryptBlowfish implements HashAlgorithm
+class CryptBlowfish extends AbstractAlgorithm
{
- use Singleton;
+ /**
+ * @var int Denotes the algorithmic cost that should be used.
+ */
+ private $cost;
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
+ * @param int $cost Denotes the algorithmic cost that should
+ * be used.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization, $cost = 10)
{
- return "Blowfish (Crypt)";
+ parent::__construct($localization);
+ $this->cost = $cost;
}
/**
@@ -51,7 +62,18 @@ class CryptBlowfish implements HashAlgorithm
*/
public function getPasswordHash($password)
{
- // TODO - add support for options: cost.
- return password_hash($password, PASSWORD_BCRYPT);
+ 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/HashAlgorithm/CryptMD5.php b/lib/Crypto/CryptMD5.php
similarity index 63%
rename from lib/HashAlgorithm/CryptMD5.php
rename to lib/Crypto/CryptMD5.php
index 2cc096e..6ca2e3b 100644
--- a/lib/HashAlgorithm/CryptMD5.php
+++ b/lib/Crypto/CryptMD5.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,25 +19,26 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* MD5 Crypt hashing implementation.
+ *
+ * @see crypt()
* @author Marcin Łojewski
*/
-class CryptMD5 extends BaseCrypt
+class CryptMD5 extends AbstractCrypt
{
- use Utils;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "MD5 (Crypt)";
+ parent::__construct($localization);
}
/**
@@ -43,6 +46,14 @@ class CryptMD5 extends BaseCrypt
*/
protected function getSalt()
{
- return "$1$" . self::randomString(8, self::SALT_ALPHABET) . "$";
+ return "$1$" . Utils::randomString(8, self::SALT_ALPHABET) . "$";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "MD5 (Crypt)";
}
}
diff --git a/lib/HashAlgorithm/CryptSHA256.php b/lib/Crypto/CryptSHA256.php
similarity index 52%
rename from lib/HashAlgorithm/CryptSHA256.php
rename to lib/Crypto/CryptSHA256.php
index ca466f9..fad91b3 100644
--- a/lib/HashAlgorithm/CryptSHA256.php
+++ b/lib/Crypto/CryptSHA256.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,25 +19,34 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* SHA256 Crypt hashing implementation.
+ *
+ * @see crypt()
* @author Marcin Łojewski
*/
-class CryptSHA256 extends BaseCrypt
+class CryptSHA256 extends AbstractCrypt
{
- use Utils;
+ /**
+ * @var int The number of rounds.
+ */
+ private $rounds;
/**
- * @inheritdoc
+ * 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 getVisibleName()
+ public function __construct(IL10N $localization, $rounds = 5000)
{
- return "SHA256 (Crypt)";
+ parent::__construct($localization);
+ $this->rounds = $rounds;
}
/**
@@ -43,7 +54,16 @@ class CryptSHA256 extends BaseCrypt
*/
protected function getSalt()
{
- // TODO - add support for options: rounds.
- return "$5\$rounds=5000$" . self::randomString(16, self::SALT_ALPHABET) . "$";
+ return "$5\$rounds=" . $this->rounds . "$" . Utils::randomString(
+ 16, self::SALT_ALPHABET
+ ) . "$";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "SHA256 (Crypt)";
}
}
diff --git a/lib/HashAlgorithm/CryptSHA512.php b/lib/Crypto/CryptSHA512.php
similarity index 52%
rename from lib/HashAlgorithm/CryptSHA512.php
rename to lib/Crypto/CryptSHA512.php
index f117606..11f3b8f 100644
--- a/lib/HashAlgorithm/CryptSHA512.php
+++ b/lib/Crypto/CryptSHA512.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,25 +19,34 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* SHA512 Crypt hashing implementation.
+ *
+ * @see crypt()
* @author Marcin Łojewski
*/
-class CryptSHA512 extends BaseCrypt
+class CryptSHA512 extends AbstractCrypt
{
- use Utils;
+ /**
+ * @var int The number of rounds.
+ */
+ private $rounds;
/**
- * @inheritdoc
+ * 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 getVisibleName()
+ public function __construct(IL10N $localization, $rounds = 5000)
{
- return "SHA512 (Crypt)";
+ parent::__construct($localization);
+ $this->rounds = $rounds;
}
/**
@@ -43,7 +54,16 @@ class CryptSHA512 extends BaseCrypt
*/
protected function getSalt()
{
- // TODO - add support for options: rounds.
- return "$5\$rounds=5000$" . self::randomString(16, self::SALT_ALPHABET) . "$";
+ return "$6\$rounds=" . $this->rounds . "$" . Utils::randomString(
+ 16, self::SALT_ALPHABET
+ ) . "$";
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "SHA512 (Crypt)";
}
}
diff --git a/lib/HashAlgorithm/CryptStandardDES.php b/lib/Crypto/CryptStandardDES.php
similarity index 65%
rename from lib/HashAlgorithm/CryptStandardDES.php
rename to lib/Crypto/CryptStandardDES.php
index 44e4a23..7d8fa7d 100644
--- a/lib/HashAlgorithm/CryptStandardDES.php
+++ b/lib/Crypto/CryptStandardDES.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,25 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* Standard DES Crypt hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class CryptStandardDES extends BaseCrypt
+class CryptStandardDES extends AbstractCrypt
{
- use Utils;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Standard DES (Crypt)";
+ parent::__construct($localization);
}
/**
@@ -43,6 +45,14 @@ class CryptStandardDES extends BaseCrypt
*/
protected function getSalt()
{
- return self::randomString(2, self::SALT_ALPHABET);
+ return Utils::randomString(2, self::SALT_ALPHABET);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "Standard DES (Crypt)";
}
}
diff --git a/lib/HashAlgorithm/Base/HashAlgorithm.php b/lib/Crypto/IPasswordAlgorithm.php
similarity index 83%
rename from lib/HashAlgorithm/Base/HashAlgorithm.php
rename to lib/Crypto/IPasswordAlgorithm.php
index 0f378a4..47ba961 100644
--- a/lib/HashAlgorithm/Base/HashAlgorithm.php
+++ b/lib/Crypto/IPasswordAlgorithm.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,24 +19,20 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm\Base;
+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 HashAlgorithm
+interface IPasswordAlgorithm
{
- /**
- * Used by reflection to get the class instance.
- * @return HashAlgorithm
- */
- public static function getInstance();
-
/**
* Get the hash algorithm name.
* This name is visible in the admin panel.
+ *
* @return string
*/
public function getVisibleName();
@@ -42,15 +40,19 @@ interface HashAlgorithm
/**
* 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.
+ * @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/HashAlgorithm/Joomla.php b/lib/Crypto/Joomla.php
similarity index 72%
rename from lib/HashAlgorithm/Joomla.php
rename to lib/Crypto/Joomla.php
index abc92af..abc9a72 100644
--- a/lib/HashAlgorithm/Joomla.php
+++ b/lib/Crypto/Joomla.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,27 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
+use OCP\IL10N;
/**
* Joomla hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class Joomla implements HashAlgorithm
+class Joomla extends AbstractAlgorithm
{
- use Singleton;
- use Utils;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "Joomla MD5 Encryption";
+ parent::__construct($localization);
}
/**
@@ -45,8 +45,12 @@ class Joomla implements HashAlgorithm
*/
public function getPasswordHash($password)
{
- return md5($password . ":" . self::randomString(32,
- "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
+ return md5(
+ $password . ":" . Utils::randomString(
+ 32,
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ )
+ );
}
/**
@@ -68,4 +72,12 @@ class Joomla implements HashAlgorithm
$pwHash .= ":" . $salt;
return $pwHash;
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "Joomla MD5 Encryption";
+ }
}
diff --git a/lib/HashAlgorithm/MD5.php b/lib/Crypto/MD5.php
similarity index 70%
rename from lib/HashAlgorithm/MD5.php
rename to lib/Crypto/MD5.php
index ad39f59..a4ba435 100644
--- a/lib/HashAlgorithm/MD5.php
+++ b/lib/Crypto/MD5.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,33 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
+use OCP\IL10N;
/**
* MD5 hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class MD5 implements HashAlgorithm
+class MD5 extends AbstractAlgorithm
{
- use Singleton;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "MD5";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, $this->getPasswordHash($password));
+ parent::__construct($localization);
}
/**
@@ -53,4 +47,12 @@ class MD5 implements HashAlgorithm
{
return md5($password);
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "MD5";
+ }
}
diff --git a/lib/HashAlgorithm/SHA1.php b/lib/Crypto/SHA1.php
similarity index 70%
rename from lib/HashAlgorithm/SHA1.php
rename to lib/Crypto/SHA1.php
index dfd8d7c..a534212 100644
--- a/lib/HashAlgorithm/SHA1.php
+++ b/lib/Crypto/SHA1.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,33 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
+use OCP\IL10N;
/**
* SHA1 hashing implementation.
+ *
* @author Marcin Łojewski
*/
-class SHA1 implements HashAlgorithm
+class SHA1 extends AbstractAlgorithm
{
- use Singleton;
-
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "SHA1";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, $this->getPasswordHash($password));
+ parent::__construct($localization);
}
/**
@@ -53,4 +47,12 @@ class SHA1 implements HashAlgorithm
{
return sha1($password);
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "SHA1";
+ }
}
diff --git a/lib/HashAlgorithm/Base/SSHA.php b/lib/Crypto/SSHA.php
similarity index 58%
rename from lib/HashAlgorithm/Base/SSHA.php
rename to lib/Crypto/SSHA.php
index baac624..cfe7119 100644
--- a/lib/HashAlgorithm/Base/SSHA.php
+++ b/lib/Crypto/SSHA.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,23 +19,35 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm\Base;
+namespace OCA\UserSQL\Crypto;
+
+use OCP\IL10N;
/**
* SSHA* hashing implementation.
+ *
* @author Marcin Łojewski
*/
-abstract class SSHA implements HashAlgorithm
+abstract class SSHA extends AbstractAlgorithm
{
- use Singleton;
- use Utils;
+ /**
+ * 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));
+ $saltedPassword = base64_decode(
+ preg_replace("/" . $this->getPrefix() . "/i", "", $dbHash)
+ );
$salt = substr($saltedPassword, -(strlen($saltedPassword) - 32));
$hash = self::ssha($password, $salt);
@@ -42,23 +56,29 @@ abstract class SSHA implements HashAlgorithm
/**
* Get hash prefix eg. {SSHA256}.
- * @return string
+ *
+ * @return string The hash prefix.
*/
public abstract function getPrefix();
/**
- * Encrypt using SSHA256 algorithm
+ * Encrypt using SSHA* algorithm.
+ *
* @param string $password The password.
- * @param string $salt The salt to use.
- * @return string The hashed password, prefixed by {SSHA256}.
+ * @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);
+ return $this->getPrefix() . base64_encode(
+ hash($this->getAlgorithm(), $password . $salt, true) . $salt
+ );
}
/**
* Get algorithm used by the hash() function.
+ *
* @see hash()
* @return string
*/
@@ -69,7 +89,10 @@ abstract class SSHA implements HashAlgorithm
*/
public function getPasswordHash($password)
{
- return self::ssha($password,
- self::randomString(32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
+ return self::ssha(
+ $password, Utils::randomString(
+ 32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ )
+ );
}
}
diff --git a/lib/HashAlgorithm/SSHA256.php b/lib/Crypto/SSHA256.php
similarity index 72%
rename from lib/HashAlgorithm/SSHA256.php
rename to lib/Crypto/SSHA256.php
index 516e0bf..a1c9d38 100644
--- a/lib/HashAlgorithm/SSHA256.php
+++ b/lib/Crypto/SSHA256.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,22 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\SSHA;
+use OCP\IL10N;
/**
* SSHA256 hashing implementation.
+ *
* @author Marcin Łojewski
*/
class SSHA256 extends SSHA
{
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "SSHA256";
+ parent::__construct($localization);
}
/**
@@ -50,4 +55,12 @@ class SSHA256 extends SSHA
{
return "sha256";
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "SSHA256";
+ }
}
diff --git a/lib/HashAlgorithm/SSHA512.php b/lib/Crypto/SSHA512.php
similarity index 72%
rename from lib/HashAlgorithm/SSHA512.php
rename to lib/Crypto/SSHA512.php
index 948a28d..b2e15d8 100644
--- a/lib/HashAlgorithm/SSHA512.php
+++ b/lib/Crypto/SSHA512.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,22 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm;
+namespace OCA\UserSQL\Crypto;
-use OCA\UserSQL\HashAlgorithm\Base\SSHA;
+use OCP\IL10N;
/**
* SSHA512 hashing implementation.
+ *
* @author Marcin Łojewski
*/
class SSHA512 extends SSHA
{
/**
- * @inheritdoc
+ * The class constructor.
+ *
+ * @param IL10N $localization The localization service.
*/
- public function getVisibleName()
+ public function __construct(IL10N $localization)
{
- return "SSHA512";
+ parent::__construct($localization);
}
/**
@@ -50,4 +55,12 @@ class SSHA512 extends SSHA
{
return "sha512";
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getAlgorithmName()
+ {
+ return "SSHA512";
+ }
}
diff --git a/lib/HashAlgorithm/Base/Utils.php b/lib/Crypto/Utils.php
similarity index 66%
rename from lib/HashAlgorithm/Base/Utils.php
rename to lib/Crypto/Utils.php
index 956c3b6..4bd0651 100644
--- a/lib/HashAlgorithm/Base/Utils.php
+++ b/lib/Crypto/Utils.php
@@ -1,7 +1,9 @@
+ *
+ * @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
@@ -17,22 +19,25 @@
* along with this program. If not, see .
*/
-namespace OCA\UserSQL\HashAlgorithm\Base;
+namespace OCA\UserSQL\Crypto;
/**
- * Cryptographic utilities trait.
+ * Cryptographic utilities.
+ *
* @author Marcin Łojewski
*/
-trait Utils
+final class Utils
{
/**
* Convert hexadecimal message to its base64 form.
- * @param $hex string Hexadecimal encoded message.
- * @return string Same message encoded in base64.
+ *
+ * @param $hex string The hexadecimal encoded message.
+ *
+ * @return string The same message encoded in base64.
*/
- private static function hexToBase64($hex)
+ public static function hexToBase64($hex)
{
- $hexChr = '';
+ $hexChr = "";
foreach (str_split($hex, 2) as $hexPair) {
$hexChr .= chr(hexdec($hexPair));
}
@@ -41,14 +46,16 @@ trait Utils
/**
* Generate random string from given alphabet.
- * @param $length int Output string length.
- * @param $alphabet string Output string alphabet.
+ *
+ * @param $length int The output string length.
+ * @param $alphabet string The output string alphabet.
+ *
* @return string Random string from given alphabet.
*/
- private static function randomString($length, $alphabet)
+ public static function randomString($length, $alphabet)
{
$string = "";
- for ($i = 0; $i != $length; ++$i) {
+ for ($idx = 0; $idx != $length; ++$idx) {
$string .= $alphabet[mt_rand(0, strlen($alphabet) - 1)];
}
return $string;
diff --git a/lib/HashAlgorithm/Crypt.php b/lib/HashAlgorithm/Crypt.php
deleted file mode 100644
index 348dbf2..0000000
--- a/lib/HashAlgorithm/Crypt.php
+++ /dev/null
@@ -1,59 +0,0 @@
-
- *
- * 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\HashAlgorithm;
-
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
-
-/**
- * Implements standard Unix DES-based algorithm or
- * alternative algorithms that may be available on the system.
- * This implementation does not support password changing.
- * @see crypt()
- * @author Marcin Łojewski
- */
-class Crypt implements HashAlgorithm
-{
- use Singleton;
-
- /**
- * @inheritdoc
- */
- public function getVisibleName()
- {
- return "Crypt (Unix)";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return hash_equals($dbHash, crypt($password, $dbHash));
- }
-
- /**
- * @inheritdoc
- */
- public function getPasswordHash($password)
- {
- return password_hash($password, PASSWORD_DEFAULT);
- }
-}
diff --git a/lib/HashAlgorithm/CryptArgon2.php b/lib/HashAlgorithm/CryptArgon2.php
deleted file mode 100644
index d628723..0000000
--- a/lib/HashAlgorithm/CryptArgon2.php
+++ /dev/null
@@ -1,57 +0,0 @@
-
- *
- * 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\HashAlgorithm;
-
-use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
-use OCA\UserSQL\HashAlgorithm\Base\Singleton;
-
-/**
- * Argon2 Crypt hashing implementation.
- * @author Marcin Łojewski
- */
-class CryptArgon2 implements HashAlgorithm
-{
- use Singleton;
-
- /**
- * @inheritdoc
- */
- public function getVisibleName()
- {
- return "Argon2 (Crypt)";
- }
-
- /**
- * @inheritdoc
- */
- public function checkPassword($password, $dbHash)
- {
- return password_verify($password, $dbHash);
- }
-
- /**
- * @inheritdoc
- */
- public function getPasswordHash($password)
- {
- // TODO - add support for options: memory_cost, time_cost, threads.
- return password_hash($password, PASSWORD_ARGON2I);
- }
-}
diff --git a/lib/HashAlgorithm/CryptExtendedDES.php b/lib/HashAlgorithm/CryptExtendedDES.php
deleted file mode 100644
index c49563f..0000000
--- a/lib/HashAlgorithm/CryptExtendedDES.php
+++ /dev/null
@@ -1,63 +0,0 @@
-
- *
- * 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\HashAlgorithm;
-
-use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
-use OCA\UserSQL\HashAlgorithm\Base\Utils;
-
-/**
- * Extended DES Crypt hashing implementation.
- * @author Marcin Łojewski
- */
-class CryptExtendedDES extends BaseCrypt
-{
- use Utils;
-
- /**
- * @inheritdoc
- */
- public function getVisibleName()
- {
- return "Extended DES (Crypt)";
- }
-
- /**
- * @inheritdoc
- */
- protected function getSalt()
- {
- // TODO - add support for options: iteration_count.
- return self::base64IntEncode(1000) . self::randomString(4, self::SALT_ALPHABET);
- }
-
- private static function base64IntEncode($number)
- {
- $alphabet = str_split(self::SALT_ALPHABET);
- $chars = array();
- $base = sizeof($alphabet);
- while ($number) {
- $rem = $number % $base;
- $number = (int)($number / $base);
- $arr[] = $alphabet[$rem];
- }
- $string = implode($chars);
- return str_pad($string, 4, '.', STR_PAD_RIGHT);
- }
-}
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 c414ad3..0000000
--- a/lib/PasswordHash.php
+++ /dev/null
@@ -1,268 +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..4de8084
--- /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, " .
+ "$gName AS name, " .
+ "$gAdmin AS admin";
+ $userColumns
+ = "$uUID AS uid, " .
+ "$uName AS name, " .
+ "$uEmail AS email, " .
+ "$uHome AS home, " .
+ "$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 bf6f5b1..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,87 +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;
+/**
+ * The administrator's settings page.
+ *
+ * @author Marcin Łojewski
+ */
class Admin implements ISettings
{
- /** @var IL10N */
- private $l10n;
- /** @var Defaults */
- private $defaults;
- /** @var IConfig */
- private $config;
+ /**
+ * @var string The application name.
+ */
+ private $appName;
+ /**
+ * @var Properties The properties array.
+ */
+ private $properties;
/**
- * @param IL10N $l10n
- * @param Defaults $defaults
- * @param IConfig $config
+ * The class constructor,
+ *
+ * @param string $AppName The application name.
+ * @param Properties $properties The properties array.
*/
- 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');
+ public function __construct($AppName, Properties $properties)
+ {
+ $this->appName = $AppName;
+ $this->properties = $properties;
}
/**
- * @return TemplateResponse
+ * @inheritdoc
*/
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;
- }
-
- $params["config"] = $this->config;
- return new TemplateResponse('user_sql', 'admin', $params);
+ return new TemplateResponse($this->appName, "admin", $this->properties->getArray());
}
/**
- * @return string the section ID, e.g. 'sharing'
+ * @inheritdoc
*/
public function getSection()
{
- return 'user_sql';
+ return $this->appName;
}
/**
- * @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"
+ * @inheritdoc
*/
public function getPriority()
{
- return 0;
+ return 25;
}
}
diff --git a/lib/Settings/Section.php b/lib/Settings/Section.php
index 9d6ba23..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,59 +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 IL10N */
- private $l;
+ /**
+ * @var string The application name.
+ */
+ private $appName;
+ /**
+ * @var IURLGenerator The URL generator.
+ */
+ private $urlGenerator;
+ /**
+ * @var IL10N The localization service.
+ */
+ private $localization;
/**
- * @param IL10N $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(IURLGenerator $url, IL10N $l)
- {
- $this->l = $l;
- $this->url = $url;
+ public function __construct(
+ $AppName, IURLGenerator $urlGenerator, IL10N $localization
+ ) {
+ $this->appName = $AppName;
+ $this->urlGenerator = $urlGenerator;
+ $this->localization = $localization;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getID()
{
- return 'user_sql';
+ return $this->appName;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getName()
{
- return $this->l->t('User SQL');
+ return $this->localization->t("SQL Backends");
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getPriority()
{
- return 75;
+ return 25;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIcon()
{
- return $this->url->imagePath('user_sql', 'app-dark.svg');
+ return $this->urlGenerator->imagePath($this->appName, "app-dark.svg");
}
}
diff --git a/lib/drupal.php b/lib/drupal.php
deleted file mode 100644
index 3b07cda..0000000
--- a/lib/drupal.php
+++ /dev/null
@@ -1,330 +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);
-}
diff --git a/lib/group_sql.php b/lib/group_sql.php
deleted file mode 100644
index 8d06cd0..0000000
--- a/lib/group_sql.php
+++ /dev/null
@@ -1,88 +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 b455e0a..0000000
--- a/lib/helper.php
+++ /dev/null
@@ -1,427 +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 Nextcloud'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
- * @return bool|string
- */
- 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
- * @return bool
- */
- 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 cb6f2a5..0000000
--- a/lib/user_sql.php
+++ /dev/null
@@ -1,981 +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\HashAlgorithm\HashAlgorithm;
-use OCP\IConfig;
-use OCP\IUser;
-use OCP\IUserSession;
-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 Nextcloud.
- * We have three (four) sync modes:
- * - none: Does nothing
- * - initial: Do the sync only once from SQL -> Nextcloud
- * - forcesql: The SQL database always wins and sync to Nextcloud
- * - forceoc: Nextcloud 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;
- }
-
- /**
- * @return HashAlgorithm|bool
- */
- private function getHashAlgorithmInstance() {
- $cryptoType = $this->settings['set_crypt_type'];
- require_once('HashAlgorithm/'. $cryptoType . '.php');
- return call_user_func('OCA\\user_sql\\HashAlgorithm\\' . $cryptoType . "::getInstance");
- }
-
- /**
- * 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;
- }
-
- $hashAlgorithm = $this->getHashAlgorithmInstance();
-
- if ($hashAlgorithm === false) {
- return false;
- }
-
- $enc_password = $hashAlgorithm->getPasswordHash($password);
-
- if ($enc_password === 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);
-
- $hashAlgorithm = $this->getHashAlgorithmInstance();
-
- if ($hashAlgorithm === false) {
- return false;
- }
-
- $ret = $hashAlgorithm->checkPassword($password, $db_pass);
-
- /*// 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 'user_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/templates/admin.php b/templates/admin.php
index a181c11..7bf64a1 100644
--- a/templates/admin.php
+++ b/templates/admin.php
@@ -102,25 +102,25 @@ $cfgClass = 'section';