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-dev agpl - Andreas Boehler <dev (at) aboehler (dot) at > - user_sql + Andreas Böhler <dev (at) aboehler (dot) at> + Marcin Łojewski <dev (at) mlojewski (dot) me> + UserSQL https://github.com/nextcloud/user_sql/issues https://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.png auth - - - 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';

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