From 9429696d929cc96c477a47a28a175581d03b5a66 Mon Sep 17 00:00:00 2001
From: Steve <stryan@cs.umd.edu>
Date: Sun, 15 Jul 2018 13:59:05 -0400
Subject: [PATCH] Added Ldap extension

---
 extensions/LdapAuthentication/.gitignore      |    7 +
 extensions/LdapAuthentication/.gitreview      |    5 +
 extensions/LdapAuthentication/.phpcs.xml      |   16 +
 .../LdapAuthentication/CODE_OF_CONDUCT.md     |    1 +
 extensions/LdapAuthentication/COPYING         |  339 +++
 extensions/LdapAuthentication/Gruntfile.js    |   29 +
 .../LdapAuthentication/LdapAuthentication.php |  153 ++
 .../LdapAuthenticationPlugin.php              | 2183 +++++++++++++++++
 .../LdapAutoAuthentication.php                |  118 +
 .../LdapPrimaryAuthenticationProvider.php     |  444 ++++
 extensions/LdapAuthentication/README          |    1 +
 extensions/LdapAuthentication/composer.json   |   19 +
 extensions/LdapAuthentication/gitinfo.json    |    1 +
 extensions/LdapAuthentication/i18n/af.json    |    8 +
 extensions/LdapAuthentication/i18n/aln.json   |    8 +
 extensions/LdapAuthentication/i18n/ar.json    |    8 +
 extensions/LdapAuthentication/i18n/ast.json   |    8 +
 .../LdapAuthentication/i18n/be-tarask.json    |    8 +
 extensions/LdapAuthentication/i18n/br.json    |    8 +
 extensions/LdapAuthentication/i18n/bs.json    |    8 +
 extensions/LdapAuthentication/i18n/ca.json    |    8 +
 extensions/LdapAuthentication/i18n/ce.json    |    8 +
 extensions/LdapAuthentication/i18n/cs.json    |    8 +
 extensions/LdapAuthentication/i18n/de.json    |    9 +
 extensions/LdapAuthentication/i18n/dsb.json   |    8 +
 extensions/LdapAuthentication/i18n/en-gb.json |    8 +
 extensions/LdapAuthentication/i18n/en.json    |    8 +
 extensions/LdapAuthentication/i18n/eo.json    |    8 +
 extensions/LdapAuthentication/i18n/es.json    |    9 +
 extensions/LdapAuthentication/i18n/fa.json    |    8 +
 extensions/LdapAuthentication/i18n/fi.json    |    8 +
 extensions/LdapAuthentication/i18n/fr.json    |    9 +
 extensions/LdapAuthentication/i18n/gl.json    |    8 +
 extensions/LdapAuthentication/i18n/gsw.json   |    8 +
 extensions/LdapAuthentication/i18n/he.json    |    8 +
 extensions/LdapAuthentication/i18n/hsb.json   |    8 +
 extensions/LdapAuthentication/i18n/hu.json    |    8 +
 extensions/LdapAuthentication/i18n/ia.json    |    8 +
 extensions/LdapAuthentication/i18n/id.json    |    8 +
 extensions/LdapAuthentication/i18n/it.json    |    8 +
 extensions/LdapAuthentication/i18n/ja.json    |    9 +
 extensions/LdapAuthentication/i18n/ko.json    |    9 +
 extensions/LdapAuthentication/i18n/ksh.json   |    8 +
 extensions/LdapAuthentication/i18n/lb.json    |    8 +
 extensions/LdapAuthentication/i18n/lv.json    |    8 +
 extensions/LdapAuthentication/i18n/mk.json    |    8 +
 extensions/LdapAuthentication/i18n/ms.json    |    8 +
 extensions/LdapAuthentication/i18n/nb.json    |    8 +
 extensions/LdapAuthentication/i18n/nl.json    |    8 +
 extensions/LdapAuthentication/i18n/oc.json    |    8 +
 extensions/LdapAuthentication/i18n/pl.json    |    8 +
 extensions/LdapAuthentication/i18n/pms.json   |    9 +
 extensions/LdapAuthentication/i18n/pt-br.json |    8 +
 extensions/LdapAuthentication/i18n/pt.json    |    8 +
 extensions/LdapAuthentication/i18n/qqq.json   |   10 +
 .../LdapAuthentication/i18n/roa-tara.json     |    8 +
 extensions/LdapAuthentication/i18n/ru.json    |    8 +
 extensions/LdapAuthentication/i18n/sco.json   |    8 +
 extensions/LdapAuthentication/i18n/sk.json    |    8 +
 extensions/LdapAuthentication/i18n/sr-ec.json |    8 +
 extensions/LdapAuthentication/i18n/sr-el.json |    4 +
 extensions/LdapAuthentication/i18n/sv.json    |    8 +
 extensions/LdapAuthentication/i18n/tl.json    |    8 +
 extensions/LdapAuthentication/i18n/tr.json    |    8 +
 extensions/LdapAuthentication/i18n/uk.json    |    8 +
 extensions/LdapAuthentication/i18n/vi.json    |    8 +
 extensions/LdapAuthentication/i18n/yue.json   |    8 +
 .../LdapAuthentication/i18n/zh-hans.json      |    8 +
 .../LdapAuthentication/i18n/zh-hant.json      |    9 +
 extensions/LdapAuthentication/package.json    |   12 +
 .../LdapAuthentication/schema/ldap-mysql.sql  |   13 +
 .../schema/ldap-postgres.sql                  |   13 +
 extensions/LdapAuthentication/version         |    4 +
 73 files changed, 3811 insertions(+)
 create mode 100644 extensions/LdapAuthentication/.gitignore
 create mode 100644 extensions/LdapAuthentication/.gitreview
 create mode 100644 extensions/LdapAuthentication/.phpcs.xml
 create mode 100644 extensions/LdapAuthentication/CODE_OF_CONDUCT.md
 create mode 100644 extensions/LdapAuthentication/COPYING
 create mode 100644 extensions/LdapAuthentication/Gruntfile.js
 create mode 100644 extensions/LdapAuthentication/LdapAuthentication.php
 create mode 100644 extensions/LdapAuthentication/LdapAuthenticationPlugin.php
 create mode 100644 extensions/LdapAuthentication/LdapAutoAuthentication.php
 create mode 100644 extensions/LdapAuthentication/LdapPrimaryAuthenticationProvider.php
 create mode 100644 extensions/LdapAuthentication/README
 create mode 100644 extensions/LdapAuthentication/composer.json
 create mode 100644 extensions/LdapAuthentication/gitinfo.json
 create mode 100644 extensions/LdapAuthentication/i18n/af.json
 create mode 100644 extensions/LdapAuthentication/i18n/aln.json
 create mode 100644 extensions/LdapAuthentication/i18n/ar.json
 create mode 100644 extensions/LdapAuthentication/i18n/ast.json
 create mode 100644 extensions/LdapAuthentication/i18n/be-tarask.json
 create mode 100644 extensions/LdapAuthentication/i18n/br.json
 create mode 100644 extensions/LdapAuthentication/i18n/bs.json
 create mode 100644 extensions/LdapAuthentication/i18n/ca.json
 create mode 100644 extensions/LdapAuthentication/i18n/ce.json
 create mode 100644 extensions/LdapAuthentication/i18n/cs.json
 create mode 100644 extensions/LdapAuthentication/i18n/de.json
 create mode 100644 extensions/LdapAuthentication/i18n/dsb.json
 create mode 100644 extensions/LdapAuthentication/i18n/en-gb.json
 create mode 100644 extensions/LdapAuthentication/i18n/en.json
 create mode 100644 extensions/LdapAuthentication/i18n/eo.json
 create mode 100644 extensions/LdapAuthentication/i18n/es.json
 create mode 100644 extensions/LdapAuthentication/i18n/fa.json
 create mode 100644 extensions/LdapAuthentication/i18n/fi.json
 create mode 100644 extensions/LdapAuthentication/i18n/fr.json
 create mode 100644 extensions/LdapAuthentication/i18n/gl.json
 create mode 100644 extensions/LdapAuthentication/i18n/gsw.json
 create mode 100644 extensions/LdapAuthentication/i18n/he.json
 create mode 100644 extensions/LdapAuthentication/i18n/hsb.json
 create mode 100644 extensions/LdapAuthentication/i18n/hu.json
 create mode 100644 extensions/LdapAuthentication/i18n/ia.json
 create mode 100644 extensions/LdapAuthentication/i18n/id.json
 create mode 100644 extensions/LdapAuthentication/i18n/it.json
 create mode 100644 extensions/LdapAuthentication/i18n/ja.json
 create mode 100644 extensions/LdapAuthentication/i18n/ko.json
 create mode 100644 extensions/LdapAuthentication/i18n/ksh.json
 create mode 100644 extensions/LdapAuthentication/i18n/lb.json
 create mode 100644 extensions/LdapAuthentication/i18n/lv.json
 create mode 100644 extensions/LdapAuthentication/i18n/mk.json
 create mode 100644 extensions/LdapAuthentication/i18n/ms.json
 create mode 100644 extensions/LdapAuthentication/i18n/nb.json
 create mode 100644 extensions/LdapAuthentication/i18n/nl.json
 create mode 100644 extensions/LdapAuthentication/i18n/oc.json
 create mode 100644 extensions/LdapAuthentication/i18n/pl.json
 create mode 100644 extensions/LdapAuthentication/i18n/pms.json
 create mode 100644 extensions/LdapAuthentication/i18n/pt-br.json
 create mode 100644 extensions/LdapAuthentication/i18n/pt.json
 create mode 100644 extensions/LdapAuthentication/i18n/qqq.json
 create mode 100644 extensions/LdapAuthentication/i18n/roa-tara.json
 create mode 100644 extensions/LdapAuthentication/i18n/ru.json
 create mode 100644 extensions/LdapAuthentication/i18n/sco.json
 create mode 100644 extensions/LdapAuthentication/i18n/sk.json
 create mode 100644 extensions/LdapAuthentication/i18n/sr-ec.json
 create mode 100644 extensions/LdapAuthentication/i18n/sr-el.json
 create mode 100644 extensions/LdapAuthentication/i18n/sv.json
 create mode 100644 extensions/LdapAuthentication/i18n/tl.json
 create mode 100644 extensions/LdapAuthentication/i18n/tr.json
 create mode 100644 extensions/LdapAuthentication/i18n/uk.json
 create mode 100644 extensions/LdapAuthentication/i18n/vi.json
 create mode 100644 extensions/LdapAuthentication/i18n/yue.json
 create mode 100644 extensions/LdapAuthentication/i18n/zh-hans.json
 create mode 100644 extensions/LdapAuthentication/i18n/zh-hant.json
 create mode 100644 extensions/LdapAuthentication/package.json
 create mode 100644 extensions/LdapAuthentication/schema/ldap-mysql.sql
 create mode 100644 extensions/LdapAuthentication/schema/ldap-postgres.sql
 create mode 100644 extensions/LdapAuthentication/version

diff --git a/extensions/LdapAuthentication/.gitignore b/extensions/LdapAuthentication/.gitignore
new file mode 100644
index 00000000..82669276
--- /dev/null
+++ b/extensions/LdapAuthentication/.gitignore
@@ -0,0 +1,7 @@
+.svn
+*~
+*.kate-swp
+.*.swp
+/node_modules
+/vendor
+composer.lock
diff --git a/extensions/LdapAuthentication/.gitreview b/extensions/LdapAuthentication/.gitreview
new file mode 100644
index 00000000..b703a586
--- /dev/null
+++ b/extensions/LdapAuthentication/.gitreview
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.wikimedia.org
+port=29418
+project=mediawiki/extensions/LdapAuthentication.git
+track=1
diff --git a/extensions/LdapAuthentication/.phpcs.xml b/extensions/LdapAuthentication/.phpcs.xml
new file mode 100644
index 00000000..65b55069
--- /dev/null
+++ b/extensions/LdapAuthentication/.phpcs.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ruleset>
+	<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
+		<exclude name="MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName" />
+		<exclude name="Squiz.Scope.MethodScope.Missing" />
+	</rule>
+	<rule ref="MediaWiki.NamingConventions.PrefixedGlobalFunctions">
+		<properties>
+			<property name="ignoreList" type="array" value="AutoAuthSetup" />
+		</properties>
+	</rule>
+	<file>.</file>
+	<arg name="extensions" value="php,php5,inc"/>
+	<arg name="encoding" value="UTF-8"/>
+</ruleset>
diff --git a/extensions/LdapAuthentication/CODE_OF_CONDUCT.md b/extensions/LdapAuthentication/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..d8e5d087
--- /dev/null
+++ b/extensions/LdapAuthentication/CODE_OF_CONDUCT.md
@@ -0,0 +1 @@
+The development of this software is covered by a [Code of Conduct](https://www.mediawiki.org/wiki/Code_of_Conduct).
diff --git a/extensions/LdapAuthentication/COPYING b/extensions/LdapAuthentication/COPYING
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/extensions/LdapAuthentication/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/extensions/LdapAuthentication/Gruntfile.js b/extensions/LdapAuthentication/Gruntfile.js
new file mode 100644
index 00000000..d9b72ffd
--- /dev/null
+++ b/extensions/LdapAuthentication/Gruntfile.js
@@ -0,0 +1,29 @@
+/*jshint node:true */
+module.exports = function ( grunt ) {
+	grunt.loadNpmTasks( 'grunt-banana-checker' );
+	grunt.loadNpmTasks( 'grunt-jsonlint' );
+	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
+
+	grunt.initConfig( {
+		banana: {
+			all: 'i18n/'
+		},
+		jshint: {
+			all: [
+				'**/*.js',
+				'!node_modules/**',
+				'!vendor/**'
+			]
+		},
+		jsonlint: {
+			all: [
+				'**/*.json',
+				'!node_modules/**',
+				'!vendor/**'
+			]
+		}
+	} );
+
+	grunt.registerTask( 'test', [ 'jsonlint', 'banana', 'jshint' ] );
+	grunt.registerTask( 'default', 'test' );
+};
diff --git a/extensions/LdapAuthentication/LdapAuthentication.php b/extensions/LdapAuthentication/LdapAuthentication.php
new file mode 100644
index 00000000..c4b5bb58
--- /dev/null
+++ b/extensions/LdapAuthentication/LdapAuthentication.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Copyright (C) 2004 Ryan Lane <http://www.mediawiki.org/wiki/User:Ryan_lane>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/**
+ * LdapAuthentication plugin. LDAP Authentication and authorization integration with MediaWiki.
+ *
+ * @file
+ * @ingroup MediaWiki
+ */
+
+/**
+ * LdapAuthentication.php
+ *
+ * Info available at https://www.mediawiki.org/wiki/Extension:LDAP_Authentication
+ * Support is available at https://www.mediawiki.org/wiki/Extension_talk:LDAP_Authentication
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+	exit;
+}
+
+$wgLDAPDomainNames = [];
+$wgLDAPServerNames = [];
+$wgLDAPUseLocal = false;
+$wgLDAPEncryptionType = [];
+$wgLDAPOptions = [];
+$wgLDAPPort = [];
+$wgLDAPSearchStrings = [];
+$wgLDAPProxyAgent = [];
+$wgLDAPProxyAgentPassword = [];
+$wgLDAPSearchAttributes = [];
+$wgLDAPBaseDNs = [];
+$wgLDAPGroupBaseDNs = [];
+$wgLDAPUserBaseDNs = [];
+$wgLDAPWriterDN = [];
+$wgLDAPWriterPassword = [];
+$wgLDAPWriteLocation = [];
+$wgLDAPAddLDAPUsers = [];
+$wgLDAPUpdateLDAP = [];
+$wgLDAPPasswordHash = [];
+$wgLDAPMailPassword = [];
+$wgLDAPPreferences = [];
+$wgLDAPDisableAutoCreate = [];
+$wgLDAPDebug = 0;
+$wgLDAPGroupUseFullDN = [];
+$wgLDAPLowerCaseUsername = [];
+$wgLDAPGroupUseRetrievedUsername = [];
+$wgLDAPGroupObjectclass = [];
+$wgLDAPGroupAttribute = [];
+$wgLDAPGroupNameAttribute = [];
+$wgLDAPGroupsUseMemberOf = [];
+$wgLDAPUseLDAPGroups = [];
+$wgLDAPLocallyManagedGroups = [];
+$wgLDAPGroupsPrevail = [];
+$wgLDAPRequiredGroups = [];
+$wgLDAPExcludedGroups = [];
+$wgLDAPGroupSearchNestedGroups = [];
+$wgLDAPAuthAttribute = [];
+$wgLDAPAutoAuthUsername = "";
+$wgLDAPAutoAuthDomain = "";
+$wgPasswordResetRoutes['domain'] = true;
+$wgLDAPActiveDirectory = [];
+$wgLDAPGroupSearchPosixPrimaryGroup = false;
+
+define( "LDAPAUTHVERSION", "2.1.0" );
+
+/**
+ * Add extension information to Special:Version
+ */
+$wgExtensionCredits['other'][] = [
+	'path' => __FILE__,
+	'name' => 'LDAP Authentication Plugin',
+	'version' => LDAPAUTHVERSION,
+	'author' => 'Ryan Lane',
+	'descriptionmsg' => 'ldapauthentication-desc',
+	'url' => 'https://www.mediawiki.org/wiki/Extension:LDAP_Authentication',
+	'license-name' => 'GPL-2.0-or-later',
+];
+
+$wgAutoloadClasses['LdapAuthenticationPlugin'] = __DIR__ . '/LdapAuthenticationPlugin.php';
+$wgAutoloadClasses['LdapPrimaryAuthenticationProvider'] =
+	__DIR__ . '/LdapPrimaryAuthenticationProvider.php';
+
+$wgMessagesDirs['LdapAuthentication'] = __DIR__ . '/i18n';
+
+# Schema changes
+$wgHooks['LoadExtensionSchemaUpdates'][] = 'efLdapAuthenticationSchemaUpdates';
+
+/**
+ * @param DatabaseUpdater $updater
+ * @return bool
+ */
+function efLdapAuthenticationSchemaUpdates( $updater ) {
+	$base = __DIR__;
+	switch ( $updater->getDB()->getType() ) {
+	case 'mysql':
+	case 'sqlite':
+		$updater->addExtensionTable( 'ldap_domains', "$base/schema/ldap-mysql.sql" );
+		break;
+	case 'postgres':
+		$updater->addExtensionTable( 'ldap_domains', "$base/schema/ldap-postgres.sql" );
+		break;
+	}
+	return true;
+}
+
+// constants for search base
+define( "GROUPDN", 0 );
+define( "USERDN", 1 );
+define( "DEFAULTDN", 2 );
+
+// constants for error reporting
+define( "NONSENSITIVE", 1 );
+define( "SENSITIVE", 2 );
+define( "HIGHLYSENSITIVE", 3 );
+
+// The auto-auth code was originally derived from the SSL Authentication plugin
+// http://www.mediawiki.org/wiki/SSL_authentication
+
+/**
+ * Sets up the auto-authentication piece of the LDAP plugin.
+ *
+ * @access public
+ */
+function AutoAuthSetup() {
+	/**
+	 * @todo If you want to make AutoAuthSetup() work in an AuthManager
+	 *  world, what you need to do is figure out how to do it with a
+	 *  SessionProvider instead of the hackiness below. You'll probably
+	 *  want an ImmutableSessionProviderWithCookie subclass where
+	 *  provideSessionInfo() does the first part of
+	 *  LdapAutoAuthentication::Authenticate() (stop before the $localId
+	 *  bit).
+	 */
+	throw new BadFunctionCallException( 'AutoAuthSetup() is not supported with AuthManager.' );
+}
diff --git a/extensions/LdapAuthentication/LdapAuthenticationPlugin.php b/extensions/LdapAuthentication/LdapAuthenticationPlugin.php
new file mode 100644
index 00000000..671f3eb6
--- /dev/null
+++ b/extensions/LdapAuthentication/LdapAuthenticationPlugin.php
@@ -0,0 +1,2183 @@
+<?php
+/**
+ * Copyright (C) 2004 Ryan Lane <http://www.mediawiki.org/wiki/User:Ryan_lane>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+class LdapAuthenticationPlugin extends AuthPlugin {
+
+	private static $instance = null;
+
+	// ldap connection resource
+	public $ldapconn;
+
+	// preferences
+	public $email, $lang, $realname, $nickname, $externalid;
+
+	// username pulled from ldap
+	public $LDAPUsername;
+
+	// userdn pulled from ldap
+	public $userdn;
+
+	// groups pulled from ldap
+	public $userLDAPGroups;
+	public $allLDAPGroups;
+
+	// boolean to test for failed auth
+	public $authFailed;
+
+	// boolean to test for fetched user info
+	public $fetchedUserInfo;
+
+	// the user's entry and all attributes
+	public $userInfo;
+
+	// the user we are currently bound as
+	public $boundAs;
+
+	/**
+	 * Fetch the singleton instance of LdapAuthenticationPlugin
+	 * @return LdapAuthenticationPlugin
+	 */
+	public static function getInstance() {
+		global $wgAuth;
+
+		if ( self::$instance === null ) {
+			if ( $wgAuth instanceof LdapAuthenticationPlugin ) {
+				self::$instance = $wgAuth;
+			} else {
+				self::$instance = new LdapAuthenticationPlugin;
+			}
+		}
+		return self::$instance;
+	}
+
+	/**
+	 * Wrapper for ldap_connect
+	 * @param null $hostname
+	 * @param int $port
+	 * @return resource|false
+	 */
+	public static function ldap_connect( $hostname = null, $port = 389 ) {
+		wfSuppressWarnings();
+		$ret = ldap_connect( $hostname, $port );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_bind
+	 * @param resource $ldapconn
+	 * @param null $dn
+	 * @param null $password
+	 * @return bool
+	 */
+	public static function ldap_bind( $ldapconn, $dn = null, $password = null ) {
+		wfSuppressWarnings();
+		$ret = ldap_bind( $ldapconn, $dn, $password );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_unbind
+	 * @param resource $ldapconn
+	 * @return bool
+	 */
+	public static function ldap_unbind( $ldapconn ) {
+		if ( $ldapconn ) {
+			wfSuppressWarnings();
+			$ret = ldap_unbind( $ldapconn );
+			wfRestoreWarnings();
+		} else {
+			$ret = false;
+		}
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_modify
+	 * @param resource $ldapconn
+	 * @param string $dn
+	 * @param array $entry
+	 * @return bool
+	 */
+	public static function ldap_modify( $ldapconn, $dn, $entry ) {
+		wfSuppressWarnings();
+		$ret = ldap_modify( $ldapconn, $dn, $entry );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_add
+	 * @param resource $ldapconn
+	 * @param string $dn
+	 * @param array $entry
+	 * @return bool
+	 */
+	public static function ldap_add( $ldapconn, $dn, $entry ) {
+		wfSuppressWarnings();
+		$ret = ldap_add( $ldapconn, $dn, $entry );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_delete
+	 * @param resource $ldapconn
+	 * @param string $dn
+	 * @return bool
+	 */
+	public static function ldap_delete( $ldapconn, $dn ) {
+		wfSuppressWarnings();
+		$ret = ldap_delete( $ldapconn, $dn );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_search
+	 * @param resource $ldapconn
+	 * @param string $basedn
+	 * @param string $filter
+	 * @param array|null $attributes
+	 * @param int|null $attrsonly
+	 * @param int|null $sizelimit
+	 * @param int|null $timelimit
+	 * @param int|null $deref
+	 * @return resource
+	 */
+	public static function ldap_search(
+		$ldapconn,
+		$basedn,
+		$filter,
+		$attributes = [],
+		$attrsonly = null,
+		$sizelimit = null,
+		$timelimit = null,
+		$deref = null
+	) {
+		wfSuppressWarnings();
+		$ret = ldap_search(
+			$ldapconn,
+			$basedn,
+			$filter,
+			$attributes,
+			$attrsonly,
+			$sizelimit,
+			$timelimit,
+			$deref
+		);
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_read
+	 * @param resource $ldapconn
+	 * @param string $basedn
+	 * @param string $filter
+	 * @param array|null $attributes
+	 * @param int|null $attrsonly
+	 * @param int|null $sizelimit
+	 * @param int|null $timelimit
+	 * @param int|null $deref
+	 * @return resource
+	 */
+	public static function ldap_read(
+		$ldapconn,
+		$basedn,
+		$filter,
+		$attributes = [],
+		$attrsonly = null,
+		$sizelimit = null,
+		$timelimit = null,
+		$deref = null
+	) {
+		wfSuppressWarnings();
+		$ret = ldap_read(
+			$ldapconn,
+			$basedn,
+			$filter,
+			$attributes,
+			$attrsonly,
+			$sizelimit,
+			$timelimit,
+			$deref
+		);
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_list
+	 * @param resource $ldapconn
+	 * @param string $basedn
+	 * @param string $filter
+	 * @param array|null $attributes
+	 * @param int|null $attrsonly
+	 * @param int|null $sizelimit
+	 * @param int|null $timelimit
+	 * @param int|null $deref
+	 * @return resource
+	 */
+	public static function ldap_list(
+		$ldapconn,
+		$basedn,
+		$filter,
+		$attributes = [],
+		$attrsonly = null,
+		$sizelimit = null,
+		$timelimit = null,
+		$deref = null
+	) {
+		wfSuppressWarnings();
+		$ret = ldap_list(
+			$ldapconn,
+			$basedn,
+			$filter,
+			$attributes,
+			$attrsonly,
+			$sizelimit,
+			$timelimit,
+			$deref
+		);
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_get_entries
+	 * @param resource $ldapconn
+	 * @param resource $resultid
+	 * @return array
+	 */
+	public static function ldap_get_entries( $ldapconn, $resultid ) {
+		wfSuppressWarnings();
+		$ret = ldap_get_entries( $ldapconn, $resultid );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_count_entries
+	 * @param resource $ldapconn
+	 * @param resource $resultid
+	 * @return int
+	 */
+	public static function ldap_count_entries( $ldapconn, $resultid ) {
+		wfSuppressWarnings();
+		$ret = ldap_count_entries( $ldapconn, $resultid );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Wrapper for ldap_errno
+	 * @param resource $ldapconn
+	 * @return int
+	 */
+	public static function ldap_errno( $ldapconn ) {
+		wfSuppressWarnings();
+		$ret = ldap_errno( $ldapconn );
+		wfRestoreWarnings();
+		return $ret;
+	}
+
+	/**
+	 * Get configuration defined by admin, or return default value
+	 *
+	 * @param string $preference
+	 * @param string $domain
+	 * @return mixed
+	 */
+	public function getConf( $preference, $domain = '' ) {
+		# Global preferences
+		switch ( $preference ) {
+		case 'DomainNames':
+			global $wgLDAPDomainNames;
+			return $wgLDAPDomainNames;
+		case 'UseLocal':
+			global $wgLDAPUseLocal;
+			return $wgLDAPUseLocal;
+		case 'AutoAuthUsername':
+			global $wgLDAPAutoAuthUsername;
+			return $wgLDAPAutoAuthUsername;
+		case 'AutoAuthDomain':
+			global $wgLDAPAutoAuthDomain;
+			return $wgLDAPAutoAuthDomain;
+		}
+
+		# Domain specific preferences
+		if ( !$domain ) {
+			$domain = $this->getDomain();
+		}
+		switch ( $preference ) {
+		case 'ServerNames':
+			global $wgLDAPServerNames;
+			return self::setOrDefault( $wgLDAPServerNames, $domain );
+		case 'EncryptionType':
+			global $wgLDAPEncryptionType;
+			return self::setOrDefault( $wgLDAPEncryptionType, $domain, 'tls' );
+		case 'Options':
+			global $wgLDAPOptions;
+			return self::setOrDefault( $wgLDAPOptions, $domain, [] );
+		case 'Port':
+			global $wgLDAPPort;
+			if ( isset( $wgLDAPPort[$domain] ) ) {
+				$this->printDebug( "Using non-standard port: " . $wgLDAPPort[$domain], SENSITIVE );
+				return (string)$wgLDAPPort[$domain];
+			} elseif ( $this->getConf( 'EncryptionType' ) == 'ssl' ) {
+				return "636";
+			} else {
+				return "389";
+			}
+		case 'SearchString':
+			global $wgLDAPSearchStrings;
+			return self::setOrDefault( $wgLDAPSearchStrings, $domain );
+		case 'ProxyAgent':
+			global $wgLDAPProxyAgent;
+			return self::setOrDefault( $wgLDAPProxyAgent, $domain );
+		case 'ProxyAgentPassword':
+			global $wgLDAPProxyAgentPassword;
+			return self::setOrDefaultPrivate( $wgLDAPProxyAgentPassword, $domain );
+		case 'SearchAttribute':
+			global $wgLDAPSearchAttributes;
+			return self::setOrDefault( $wgLDAPSearchAttributes, $domain );
+		case 'BaseDN':
+			global $wgLDAPBaseDNs;
+			return self::setOrDefault( $wgLDAPBaseDNs, $domain );
+		case 'GroupBaseDN':
+			global $wgLDAPGroupBaseDNs;
+			return self::setOrDefault( $wgLDAPGroupBaseDNs, $domain );
+		case 'UserBaseDN':
+			global $wgLDAPUserBaseDNs;
+			return self::setOrDefault( $wgLDAPUserBaseDNs, $domain );
+		case 'WriterDN':
+			global $wgLDAPWriterDN;
+			return self::setOrDefault( $wgLDAPWriterDN, $domain );
+		case 'WriterPassword':
+			global $wgLDAPWriterPassword;
+			return self::setOrDefaultPrivate( $wgLDAPWriterPassword, $domain );
+		case 'WriteLocation':
+			global $wgLDAPWriteLocation;
+			return self::setOrDefault( $wgLDAPWriteLocation, $domain );
+		case 'AddLDAPUsers':
+			global $wgLDAPAddLDAPUsers;
+			return self::setOrDefault( $wgLDAPAddLDAPUsers, $domain, false );
+		case 'UpdateLDAP':
+			global $wgLDAPUpdateLDAP;
+			return self::setOrDefault( $wgLDAPUpdateLDAP, $domain, false );
+		case 'PasswordHash':
+			global $wgLDAPPasswordHash;
+			return self::setOrDefaultPrivate( $wgLDAPPasswordHash, $domain, 'clear' );
+		case 'MailPassword':
+			global $wgLDAPMailPassword;
+			return self::setOrDefaultPrivate( $wgLDAPMailPassword, $domain, false );
+		case 'Preferences':
+			global $wgLDAPPreferences;
+			return self::setOrDefault( $wgLDAPPreferences, $domain, [] );
+		case 'DisableAutoCreate':
+			global $wgLDAPDisableAutoCreate;
+			return self::setOrDefault( $wgLDAPDisableAutoCreate, $domain, false );
+		case 'GroupUseFullDN':
+			global $wgLDAPGroupUseFullDN;
+			return self::setOrDefault( $wgLDAPGroupUseFullDN, $domain, false );
+		case 'LowerCaseUsername':
+			global $wgLDAPLowerCaseUsername;
+			// Default set to true for backwards compatibility with
+			// versions < 2.0a
+			return self::setOrDefault( $wgLDAPLowerCaseUsername, $domain, true );
+		case 'GroupUseRetrievedUsername':
+			global $wgLDAPGroupUseRetrievedUsername;
+			return self::setOrDefault( $wgLDAPGroupUseRetrievedUsername, $domain, false );
+		case 'GroupObjectclass':
+			global $wgLDAPGroupObjectclass;
+			return self::setOrDefault( $wgLDAPGroupObjectclass, $domain );
+		case 'GroupAttribute':
+			global $wgLDAPGroupAttribute;
+			return self::setOrDefault( $wgLDAPGroupAttribute, $domain );
+		case 'GroupNameAttribute':
+			global $wgLDAPGroupNameAttribute;
+			return self::setOrDefault( $wgLDAPGroupNameAttribute, $domain );
+		case 'GroupsUseMemberOf':
+			global $wgLDAPGroupsUseMemberOf;
+			return self::setOrDefault( $wgLDAPGroupsUseMemberOf, $domain, false );
+		case 'UseLDAPGroups':
+			global $wgLDAPUseLDAPGroups;
+			return self::setOrDefault( $wgLDAPUseLDAPGroups, $domain, false );
+		case 'LocallyManagedGroups':
+			global $wgLDAPLocallyManagedGroups;
+			return self::setOrDefault( $wgLDAPLocallyManagedGroups, $domain, [] );
+		case 'GroupsPrevail':
+			global $wgLDAPGroupsPrevail;
+			return self::setOrDefault( $wgLDAPGroupsPrevail, $domain, false );
+		case 'RequiredGroups':
+			global $wgLDAPRequiredGroups;
+			return self::setOrDefault( $wgLDAPRequiredGroups, $domain, [] );
+		case 'ExcludedGroups':
+			global $wgLDAPExcludedGroups;
+			return self::setOrDefault( $wgLDAPExcludedGroups, $domain, [] );
+		case 'GroupSearchNestedGroups':
+			global $wgLDAPGroupSearchNestedGroups;
+			return self::setOrDefault( $wgLDAPGroupSearchNestedGroups, $domain, false );
+		case 'AuthAttribute':
+			global $wgLDAPAuthAttribute;
+			return self::setOrDefault( $wgLDAPAuthAttribute, $domain );
+		case 'ActiveDirectory':
+			global $wgLDAPActiveDirectory;
+			return self::setOrDefault( $wgLDAPActiveDirectory, $domain, false );
+		case 'GroupSearchPosixPrimaryGroup':
+			global $wgLDAPGroupSearchPosixPrimaryGroup;
+			return self::setOrDefault( $wgLDAPGroupSearchPosixPrimaryGroup, $domain, false );
+		}
+		return '';
+	}
+
+	/**
+	 * Returns the item from $array at index $key if it is set,
+	 * else, it returns $default
+	 *
+	 * @param array $array
+	 * @param string $key
+	 * @param mixed $default
+	 * @return mixed
+	 */
+	private static function setOrDefault( $array, $key, $default = '' ) {
+		return isset( $array[$key] ) ? $array[$key] : $default;
+	}
+
+	/**
+	 * Returns the item from $array at index $key if it is set,
+	 * else, it returns $default
+	 *
+	 * Use for sensitive data
+	 *
+	 * @param array $array
+	 * @param string $key
+	 * @param mixed $default
+	 * @return mixed
+	 */
+	private static function setOrDefaultPrivate( $array, $key, $default = '' ) {
+		return isset( $array[$key] ) ? $array[$key] : $default;
+	}
+
+	/**
+	 * Check whether there exists a user account with the given name.
+	 * The name will be normalized to MediaWiki's requirements, so
+	 * you might need to munge it (for instance, for lowercase initial
+	 * letters).
+	 *
+	 * @param string $username
+	 * @return bool
+	 */
+	public function userExists( $username ) {
+		$this->printDebug( "Entering userExists", NONSENSITIVE );
+
+		// If we can't add LDAP users, we don't really need to check
+		// if the user exists, the authenticate method will do this for
+		// us. This will decrease hits to the LDAP server.
+		// We do however, need to use this if we are using auto authentication.
+		if ( !$this->getConf( 'AddLDAPUsers' ) && !$this->useAutoAuth() ) {
+			return true;
+		}
+
+		return $this->userExistsReal( $username );
+	}
+
+	/**
+	 * Like self::userExists, but always does the check
+	 * @see self::userExists()
+	 * @param string $username
+	 * @return bool
+	 */
+	public function userExistsReal( $username ) {
+		$this->printDebug( "Entering userExistsReal", NONSENSITIVE );
+
+		$ret = false;
+		if ( $this->connect() ) {
+			$searchstring = $this->getSearchString( $username );
+
+			if ( $searchstring == '' ) {
+				// It is possible that getSearchString will return an
+				// empty string, which means "no user".
+			} elseif ( $this->useAutoAuth() ) {
+				// If we are using auto authentication, and we got
+				// anything back, then the user exists.
+				$ret = true;
+			} else {
+				// Search for the entry.
+				$entry = self::ldap_read(
+					$this->ldapconn, $searchstring, "objectclass=*"
+				);
+				if ( $entry &&
+					self::ldap_count_entries( $this->ldapconn, $entry ) > 0
+				) {
+					$this->printDebug( "Found a matching user in LDAP", NONSENSITIVE );
+					$ret = true;
+				} else {
+					$this->printDebug( "Did not find a matching user in LDAP", NONSENSITIVE );
+				}
+			}
+			// getSearchString is going to bind, but will not unbind
+			self::ldap_unbind( $this->ldapconn );
+		}
+		return $ret;
+	}
+
+	/**
+	 * Connect to LDAP
+	 * @param string $domain
+	 * @return bool
+	 */
+	public function connect( $domain = '' ) {
+		$this->printDebug( "Entering Connect", NONSENSITIVE );
+
+		if ( !function_exists( 'ldap_connect' ) ) {
+			$this->printDebug( "It looks like you are missing LDAP support; please ensure you " .
+				"have either compiled LDAP support in, or have enabled the module. If the " .
+				"authentication is working for you, the plugin isn't properly detecting the LDAP " .
+				"module, and you can safely ignore this message.", NONSENSITIVE );
+			return false;
+		}
+
+		// Set the server string depending on whether we use ssl or not
+		$encryptionType = $this->getConf( 'EncryptionType', $domain );
+		switch ( $encryptionType ) {
+			case "ldapi":
+				$this->printDebug( "Using ldapi", SENSITIVE );
+				$serverpre = "ldapi://";
+				break;
+			case "ssl":
+				$this->printDebug( "Using SSL", SENSITIVE );
+				$serverpre = "ldaps://";
+				break;
+			default:
+				$this->printDebug( "Using TLS or not using encryption.", SENSITIVE );
+				$serverpre = "ldap://";
+		}
+
+		// Make a space separated list of server strings with the connection type
+		// string added.
+		$servers = "";
+		$tmpservers = $this->getConf( 'ServerNames', $domain );
+		$tok = strtok( $tmpservers, " " );
+		while ( $tok ) {
+			$servers = $servers . " " . $serverpre . $tok . ":" . $this->getConf( 'Port', $domain );
+			$tok = strtok( " " );
+		}
+		$servers = trim( $servers );
+		if ( !$servers ) {
+			$this->printDebug( 'Empty server string, skipping connection', NONSENSITIVE );
+			return false;
+		}
+
+		$this->printDebug( "Using servers: $servers", SENSITIVE );
+
+		// Connect and set options
+		$this->ldapconn = self::ldap_connect( $servers );
+		if ( !$this->ldapconn ) {
+			$this->printDebug( "PHP's LDAP connect method returned null, this likely implies a " .
+				"misconfiguration of the plugin.", NONSENSITIVE );
+			return false;
+		}
+		ldap_set_option( $this->ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3 );
+		ldap_set_option( $this->ldapconn, LDAP_OPT_REFERRALS, 0 );
+
+		foreach ( $this->getConf( 'Options' )  as $key => $value ) {
+			if ( !ldap_set_option( $this->ldapconn, constant( $key ), $value ) ) {
+				$this->printDebug(
+					"Can't set option to LDAP! Option code and value: " . $key . "=" . $value, 1
+				);
+			}
+		}
+
+		// TLS needs to be started after the connection resource is available
+		if ( $encryptionType == "tls" ) {
+			$this->printDebug( "Using TLS", SENSITIVE );
+			if ( !ldap_start_tls( $this->ldapconn ) ) {
+				$this->printDebug( "Failed to start TLS.", SENSITIVE );
+				return false;
+			}
+		}
+		$this->printDebug( "PHP's LDAP connect method returned true (note, this does not imply " .
+			"it connected to the server).", NONSENSITIVE );
+
+		return true;
+	}
+
+	/**
+	 * Check if a username+password pair is a valid login, or if the username
+	 * is allowed access to the wiki.
+	 * The name will be normalized to MediaWiki's requirements, so
+	 * you might need to munge it (for instance, for lowercase initial
+	 * letters).
+	 *
+	 * @param string $username
+	 * @param string $password
+	 * @return bool
+	 */
+	public function authenticate( $username, $password = '' ) {
+		$this->printDebug( "Entering authenticate for username $username", NONSENSITIVE );
+
+		// We don't handle local authentication
+		if ( 'local' == $this->getDomain() ) {
+			$this->printDebug( "User is using a local domain", SENSITIVE );
+			return false;
+		}
+
+		// Mediawiki munges the username before authenticate is called,
+		// this can mess with authentication, group pulling/restriction,
+		// preference pulling, etc. Let's allow the admin to use
+		// a lowercased username if needed.
+		if ( $this->getConf( 'LowerCaseUsername' ) ) {
+			$username = strtolower( $username );
+		}
+
+		// If the user is using auto authentication, we need to ensure
+		// that he/she isn't trying to fool us by sending a username other
+		// than the one the web server got from the auto-authentication method.
+		if ( $this->useAutoAuth() && $this->getConf( 'AutoAuthUsername' ) != $username ) {
+			$this->printDebug( "The username provided ($username) doesn't match the username " .
+				"provided by the webserver (" . $this->getConf( 'AutoAuthUsername' ) . "). " .
+				"The user is probably trying to log in to the auto-authentication domain with " .
+				"password authentication via the wiki. Denying access.", SENSITIVE );
+			return false;
+		}
+
+		// We need to ensure that if we require a password, that it is
+		// not blank. We don't allow blank passwords, so we are being
+		// tricked if someone is supplying one when using password auth.
+		// auto-authentication is handled by the webserver; a blank password
+		// here is wanted.
+		if ( '' == $password && !$this->useAutoAuth() ) {
+			$this->printDebug( "User used a blank password", NONSENSITIVE );
+			return false;
+		}
+
+		if ( $this->connect() ) {
+			$this->userdn = $this->getSearchString( $username );
+
+			// It is possible that getSearchString will return an
+			// empty string; if this happens, the bind will ALWAYS
+			// return true, and will let anyone in!
+			if ( '' == $this->userdn ) {
+				$this->printDebug( "User DN is blank", NONSENSITIVE );
+				self::ldap_unbind( $this->ldapconn );
+				$this->markAuthFailed();
+				return false;
+			}
+
+			// If we are using password authentication, we need to bind as the
+			// user to make sure the password is correct.
+			if ( !$this->useAutoAuth() ) {
+				$this->printDebug( "Binding as the user", NONSENSITIVE );
+				$bind = $this->bindAs( $this->userdn, $password );
+				if ( !$bind ) {
+					$this->markAuthFailed();
+					return false;
+				}
+				$result = true;
+				Hooks::run( 'ChainAuth', [ $username, $password, &$result ] );
+				if ( $result == false ) {
+					return false;
+				}
+
+				$this->printDebug( "Bound successfully", NONSENSITIVE );
+
+				$ss = $this->getConf( 'SearchString' );
+				if ( $ss ) {
+					if ( strstr( $ss, "@" ) || strstr( $ss, '\\' ) ) {
+						// We are most likely configured using USER-NAME@DOMAIN, or
+						// DOMAIN\\USER-NAME.
+						// Get the user's full DN so we can search for groups and such.
+						$this->userdn = $this->getUserDN( $username );
+						$this->printDebug( "Fetched UserDN: $this->userdn", NONSENSITIVE );
+					} else {
+						// Now that we are bound, we can pull the user's info.
+						$this->getUserInfo();
+					}
+				}
+			}
+
+			// Ensure the user's entry has the required auth attribute
+			$aa = $this->getConf( 'AuthAttribute' );
+			if ( $aa ) {
+				$this->printDebug( "Checking for auth attributes: $aa", NONSENSITIVE );
+				$filter = "(" . $aa . ")";
+				$attributes = [ "dn" ];
+				$entry = self::ldap_read(
+					$this->ldapconn, $this->userdn, $filter, $attributes
+				);
+				$info = self::ldap_get_entries( $this->ldapconn, $entry );
+				if ( $info["count"] < 1 ) {
+					$this->printDebug( "Failed auth attribute check", NONSENSITIVE );
+					self::ldap_unbind( $this->ldapconn );
+					$this->markAuthFailed();
+					return false;
+				}
+			}
+
+			$this->getGroups( $username );
+
+			if ( !$this->checkGroups() ) {
+				self::ldap_unbind( $this->ldapconn );
+				$this->markAuthFailed();
+				return false;
+			}
+
+			$this->getPreferences();
+
+			self::ldap_unbind( $this->ldapconn );
+		} else {
+			$this->markAuthFailed();
+			return false;
+		}
+		$this->printDebug( "Authentication passed", NONSENSITIVE );
+
+		// We made it this far; the user authenticated and didn't fail any checks, so he/she gets in
+		return true;
+	}
+
+	function markAuthFailed() {
+		$this->authFailed = true;
+	}
+
+	/**
+	 * Modify options in the login template.
+	 *
+	 * @param UserLoginTemplate &$template
+	 * @param string &$type
+	 */
+	public function modifyUITemplate( &$template, &$type ) {
+		$this->printDebug( "Entering modifyUITemplate", NONSENSITIVE );
+		$template->set( 'create', $this->getConf( 'AddLDAPUsers' ) );
+		$template->set( 'usedomain', true );
+		$template->set( 'useemail', $this->getConf( 'MailPassword' ) );
+		$template->set( 'canreset', $this->getConf( 'MailPassword' ) );
+		$template->set( 'domainnames', $this->domainList() );
+		Hooks::run( 'LDAPModifyUITemplate', [ &$template ] );
+	}
+
+	/**
+	 * @return array
+	 */
+	function domainList() {
+		$tempDomArr = $this->getConf( 'DomainNames' );
+		if ( $this->getConf( 'UseLocal' ) ) {
+			$this->printDebug( "Allowing the local domain, adding it to the list.", NONSENSITIVE );
+			array_push( $tempDomArr, 'local' );
+		}
+
+		if ( $this->getConf( 'AutoAuthDomain' ) ) {
+			$this->printDebug(
+				"Allowing auto-authentication login, removing the domain from the list.",
+				NONSENSITIVE
+			);
+			// There is no reason for people to log in directly to the wiki if the are using an
+			// auto-authentication domain. If they try to, they are probably up to something fishy.
+			unset( $tempDomArr[array_search( $this->getConf( 'AutoAuthDomain' ), $tempDomArr )] );
+		}
+
+		$domains = [];
+		foreach ( $tempDomArr as $tempDom ) {
+			$domains["$tempDom"] = $tempDom;
+		}
+		return $domains;
+	}
+
+	/**
+	 * Return true if the wiki should create a new local account automatically
+	 * when asked to login a user who doesn't exist locally but does in the
+	 * external auth database.
+	 *
+	 * This is just a question, and shouldn't perform any actions.
+	 *
+	 * @return bool
+	 */
+	public function autoCreate() {
+		return !$this->getConf( 'DisableAutoCreate' );
+	}
+
+	/**
+	 * Set the given password in LDAP.
+	 * Return true if successful.
+	 *
+	 * @param User $user
+	 * @param string $password
+	 * @return bool
+	 */
+	public function setPassword( $user, $password ) {
+		$this->printDebug( "Entering setPassword", NONSENSITIVE );
+
+		if ( $this->getDomain() == 'local' ) {
+			$this->printDebug( "User is using a local domain", NONSENSITIVE );
+
+			// We don't set local passwords, but we don't want the wiki
+			// to send the user a failure.
+			return true;
+		}
+		if ( !$this->getConf( 'UpdateLDAP' ) ) {
+			$this->printDebug( "Wiki is set to not allow updates", NONSENSITIVE );
+
+			// We aren't allowing the user to change his/her own password
+			return false;
+		}
+
+		$writer = $this->getConf( 'WriterDN' );
+		if ( !$writer ) {
+			$this->printDebug( "Wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE );
+
+			// We can't change a user's password without an account that is
+			// allowed to do it.
+			return false;
+		}
+		$pass = $this->getPasswordHash( $password );
+
+		if ( $this->connect() ) {
+			$this->userdn = $this->getSearchString( $user->getName() );
+			$this->printDebug( "Binding as the writerDN", NONSENSITIVE );
+			$bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) );
+			if ( !$bind ) {
+				return false;
+			}
+			$values["userpassword"] = $pass;
+
+			// Blank out the password in the database. We don't want to save
+			// domain credentials for security reasons.
+			// This doesn't do anything. $password isn't by reference
+			$password = '';
+
+			$success = self::ldap_modify(
+				$this->ldapconn, $this->userdn, $values
+			);
+			self::ldap_unbind( $this->ldapconn );
+			if ( $success ) {
+				$this->printDebug( "Successfully modified the user's password", NONSENSITIVE );
+				return true;
+			}
+			$this->printDebug( "Failed to modify the user's password", NONSENSITIVE );
+		}
+		return false;
+	}
+
+	/**
+	 * Update user information in LDAP
+	 * Return true if successful.
+	 *
+	 * @param User $user
+	 * @return bool
+	 */
+	public function updateExternalDB( $user ) {
+		global $wgMemc;
+
+		$this->printDebug( "Entering updateExternalDB", NONSENSITIVE );
+		if ( !$this->getConf( 'UpdateLDAP' ) || $this->getDomain() == 'local' ) {
+			$this->printDebug(
+				"Either the user is using a local domain, or the wiki isn't allowing updates",
+				NONSENSITIVE
+			);
+			// We don't handle local preferences, but we don't want the
+			// wiki to return an error.
+			return true;
+		}
+
+		$writer = $this->getConf( 'WriterDN' );
+		if ( !$writer ) {
+			$this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE );
+			// We can't modify LDAP preferences if we don't have a user
+			// capable of editing LDAP attributes.
+			return false;
+		}
+
+		$this->email = $user->getEmail();
+		$this->realname = $user->getRealName();
+		$this->nickname = $user->getOption( 'nickname' );
+		$this->lang = $user->getOption( 'language' );
+		if ( $this->connect() ) {
+			$this->userdn = $this->getSearchString( $user->getName() );
+			$this->printDebug( "Binding as the writerDN", NONSENSITIVE );
+			$bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) );
+			if ( !$bind ) {
+				return false;
+			}
+
+			$values = [];
+			$prefs = $this->getConf( 'Preferences' );
+			foreach ( array_keys( $prefs ) as $key ) {
+				$attr = strtolower( $prefs[$key] );
+				switch ( $key ) {
+					case "email":
+						if ( is_string( $this->email ) ) {
+							$values[$attr] = $this->email;
+						}
+						break;
+					case "nickname":
+						if ( is_string( $this->nickname ) ) {
+							$values[$attr] = $this->nickname;
+						}
+						break;
+					case "realname":
+						if ( is_string( $this->realname ) ) {
+							$values[$attr] = $this->realname;
+						}
+						break;
+					case "language":
+						if ( is_string( $this->lang ) ) {
+							$values[$attr] = $this->lang;
+						}
+						break;
+				}
+			}
+
+			if ( count( $values ) &&
+				self::ldap_modify( $this->ldapconn, $this->userdn, $values )
+			) {
+				// We changed the user, we need to invalidate the memcache key
+				$key = wfMemcKey( 'ldapauthentication', 'userinfo', $this->userdn );
+				$wgMemc->delete( $key );
+				$this->printDebug( "Successfully modified the user's attributes", NONSENSITIVE );
+				self::ldap_unbind( $this->ldapconn );
+				return true;
+			}
+			$this->printDebug( "Failed to modify the user's attributes", NONSENSITIVE );
+			self::ldap_unbind( $this->ldapconn );
+		}
+		return false;
+	}
+
+	/**
+	 * Can the wiki create accounts in LDAP?
+	 * Return true if yes.
+	 *
+	 * @return bool
+	 */
+	public function canCreateAccounts() {
+		return $this->getConf( 'AddLDAPUsers' );
+	}
+
+	/**
+	 * Can the wiki change passwords in LDAP, or can the user
+	 * change passwords locally?
+	 * Return true if yes.
+	 *
+	 * @return bool
+	 */
+	public function allowPasswordChange() {
+		$this->printDebug( "Entering allowPasswordChange", NONSENSITIVE );
+
+		// Local domains need to be able to change passwords
+		if ( $this->getConf( 'UseLocal' ) && 'local' == $this->getDomain() ) {
+			return true;
+		}
+		if ( $this->getConf( 'UpdateLDAP' ) || $this->getConf( 'MailPassword' ) ) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Disallow MediaWiki from setting local passwords in the database,
+	 * unless UseLocal is true. Warning: if you set $wgLDAPUseLocal,
+	 * it will cause MediaWiki to leak LDAP passwords into the local database.
+	 * @return bool
+	 */
+	public function allowSetLocalPassword() {
+		return $this->getConf( 'UseLocal' );
+	}
+
+	/**
+	 * Add a user to LDAP.
+	 * Return true if successful.
+	 *
+	 * @param User $user
+	 * @param string $password
+	 * @param string $email
+	 * @param string $realname
+	 * @return bool
+	 */
+	public function addUser( $user, $password, $email = '', $realname = '' ) {
+		$this->printDebug( "Entering addUser", NONSENSITIVE );
+
+		if ( !$this->getConf( 'AddLDAPUsers' ) || 'local' == $this->getDomain() ) {
+			$this->printDebug( "Either the user is using a local domain, or the wiki isn't " .
+				"allowing users to be added to LDAP", NONSENSITIVE );
+
+			// Tell the wiki not to return an error.
+			return true;
+		}
+		if ( $this->getConf( 'RequiredGroups' ) ) {
+			$this->printDebug( "The wiki is requiring users to be in specific groups, and cannot " .
+				"add users as this would be a security hole.", NONSENSITIVE );
+			// It is possible that later we can add users into
+			// groups, but since we don't support it, we don't want
+			// to open holes!
+			return false;
+		}
+
+		$writer = $this->getConf( 'WriterDN' );
+		if ( !$writer ) {
+			$this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE );
+
+			// We can't add users without an LDAP account capable of doing so.
+			return false;
+		}
+
+		$this->email = $user->getEmail();
+		$this->realname = $user->getRealName();
+		$username = $user->getName();
+		if ( $this->getConf( 'LowerCaseUsername' ) ) {
+			$username = strtolower( $username );
+		}
+		$pass = $this->getPasswordHash( $password );
+		if ( $this->connect() ) {
+			$writeloc = $this->getConf( 'WriteLocation' );
+			$this->userdn = $this->getSearchString( $username );
+			if ( '' == $this->userdn ) {
+				$this->printDebug(
+					"userdn is blank, attempting to use wgLDAPWriteLocation", NONSENSITIVE
+				);
+				if ( $writeloc ) {
+					$this->printDebug( "wgLDAPWriteLocation is set, using that", NONSENSITIVE );
+					$this->userdn = $this->getConf( 'SearchAttribute' ) . "=" .
+						$username . "," . $writeloc;
+				} else {
+					$this->printDebug( "wgLDAPWriteLocation is not set, failing", NONSENSITIVE );
+					// getSearchString will bind, but will not unbind
+					self::ldap_unbind( $this->ldapconn );
+					return false;
+				}
+			}
+
+			$this->printDebug( "Binding as the writerDN", NONSENSITIVE );
+			$bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) );
+			if ( !$bind ) {
+				$this->printDebug( "Failed to bind as the writerDN; add failed", NONSENSITIVE );
+				return false;
+			}
+
+			// Set up LDAP objectclasses and attributes
+			// TODO: make objectclasses and attributes configurable
+			$values["uid"] = $username;
+			// sn is required for objectclass inetorgperson
+			$values["sn"] = $username;
+			$prefs = $this->getConf( 'Preferences' );
+			foreach ( array_keys( $prefs ) as $key ) {
+				$attr = strtolower( $prefs[$key] );
+				switch ( $key ) {
+					case "email":
+						if ( is_string( $this->email ) ) {
+							$values[$attr] = $this->email;
+						}
+						break;
+					case "nickname":
+						if ( is_string( $this->nickname ) ) {
+							$values[$attr] = $this->nickname;
+						}
+						break;
+					case "realname":
+						if ( is_string( $this->realname ) ) {
+							$values[$attr] = $this->realname;
+						}
+						break;
+					case "language":
+						if ( is_string( $this->lang ) ) {
+							$values[$attr] = $this->lang;
+						}
+						break;
+				}
+			}
+			if ( !array_key_exists( "cn", $values ) ) {
+				$values["cn"] = $username;
+			}
+			$values["userpassword"] = $pass;
+			$values["objectclass"] = [ "inetorgperson" ];
+
+			$result = true;
+			# Let other extensions modify the user object before creation
+			Hooks::run( 'LDAPSetCreationValues',
+				[ $this, $username, &$values, $writeloc, &$this->userdn, &$result ] );
+			if ( !$result ) {
+				$this->printDebug(
+					"Failed to add user because LDAPSetCreationValues returned false", NONSENSITIVE
+				);
+				self::ldap_unbind( $this->ldapconn );
+				return false;
+			}
+
+			$this->printDebug( "Adding user", NONSENSITIVE );
+			if ( self::ldap_add( $this->ldapconn, $this->userdn, $values ) ) {
+				$this->printDebug( "Successfully added user", NONSENSITIVE );
+				self::ldap_unbind( $this->ldapconn );
+				return true;
+			}
+			$errno = self::ldap_errno( $this->ldapconn );
+			# Constraint violation, let's allow other plugins a chance to retry
+			if ( $errno === 19 ) {
+				$result = false;
+				Hooks::run( 'LDAPRetrySetCreationValues',
+					[ $this, $username, &$values, $writeloc, &$this->userdn, &$result ] );
+				if ( $result &&
+					self::ldap_add( $this->ldapconn, $this->userdn, $values )
+				) {
+					$this->printDebug( "Successfully added user", NONSENSITIVE );
+					self::ldap_unbind( $this->ldapconn );
+					return true;
+				}
+			}
+			$this->printDebug( "Failed to add user", NONSENSITIVE );
+			self::ldap_unbind( $this->ldapconn );
+		}
+		return false;
+	}
+
+	/**
+	 * Set the domain this plugin is supposed to use when authenticating.
+	 *
+	 * @param string $domain
+	 */
+	public function setDomain( $domain ) {
+		$this->printDebug( "Setting domain as: $domain", NONSENSITIVE );
+		$_SESSION['wsDomain'] = $domain;
+	}
+
+	/**
+	 * Get the user's domain
+	 *
+	 * @return string
+	 */
+	public function getDomain() {
+		global $wgUser;
+
+		$this->printDebug( "Entering getDomain", NONSENSITIVE );
+
+		# If there's only a single domain set, there's no reason
+		# to bother with sessions, tokens, etc.. This works around
+		# a number of bugs caused by supporting multiple domains.
+		# The bugs will still exist when using multiple domains,
+		# though.
+		$domainNames = $this->getConf( 'DomainNames' );
+		if ( ( count( $domainNames ) === 1 ) && !$this->getConf( 'UseLocal' ) ) {
+			return $domainNames[0];
+		}
+		# First check if we already have a valid domain set
+		if ( isset( $_SESSION['wsDomain'] ) && $_SESSION['wsDomain'] != 'invaliddomain' ) {
+			$this->printDebug( "Pulling domain from session.", NONSENSITIVE );
+			return $_SESSION['wsDomain'];
+		}
+		# If the session domain isn't set, the user may have been logged
+		# in with a token, check the user options.
+		# If $wgUser isn't defined yet, it might be due to an LDAPAutoAuthDomain config.
+		if ( isset( $wgUser ) && $wgUser->isLoggedIn() && $wgUser->getToken( false ) ) {
+			$this->printDebug( "Pulling domain from user options.", NONSENSITIVE );
+			$domain = self::loadDomain( $wgUser );
+			if ( $domain ) {
+				return $domain;
+			}
+		}
+		# The user must be using an invalid domain
+		$this->printDebug( "No domain found, returning invaliddomain", NONSENSITIVE );
+		return 'invaliddomain';
+	}
+
+	/**
+	 * Check to see if the specific domain is a valid domain.
+	 * Return true if the domain is valid.
+	 *
+	 * @param string $domain
+	 * @return bool
+	 */
+	public function validDomain( $domain ) {
+		$this->printDebug( "Entering validDomain", NONSENSITIVE );
+		if ( in_array( $domain, $this->getConf( 'DomainNames' ) ) ||
+			( $this->getConf( 'UseLocal' ) && 'local' == $domain )
+		) {
+			$this->printDebug( "User is using a valid domain ($domain).", NONSENSITIVE );
+			return true;
+		}
+		$this->printDebug( "User is not using a valid domain ($domain).", NONSENSITIVE );
+		return false;
+	}
+
+	/**
+	 * When a user logs in, update user with information from LDAP.
+	 *
+	 * @param User &$user
+	 * TODO: fix the setExternalID stuff
+	 */
+	public function updateUser( &$user ) {
+		$this->printDebug( "Entering updateUser", NONSENSITIVE );
+		if ( $this->authFailed ) {
+			$this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE );
+			return;
+		}
+
+		if ( $this->getConf( 'Preferences' ) ) {
+			$this->printDebug( "Setting user preferences.", NONSENSITIVE );
+			if ( is_string( $this->lang ) ) {
+				$this->printDebug( "Setting language.", NONSENSITIVE );
+				$user->setOption( 'language', $this->lang );
+			}
+			if ( is_string( $this->nickname ) ) {
+				$this->printDebug( "Setting nickname.", NONSENSITIVE );
+				$user->setOption( 'nickname', $this->nickname );
+			}
+			if ( is_string( $this->realname ) ) {
+				$this->printDebug( "Setting realname.", NONSENSITIVE );
+				$user->setRealName( $this->realname );
+			}
+			if ( is_string( $this->email ) ) {
+				$this->printDebug( "Setting email.", NONSENSITIVE );
+				$user->setEmail( $this->email );
+				$user->confirmEmail();
+			}
+		}
+
+		if ( $this->getConf( 'UseLDAPGroups' ) ) {
+			$this->printDebug( "Setting user groups.", NONSENSITIVE );
+			$this->setGroups( $user );
+		}
+
+		# We must set a user option if we want token based logins to work
+		if ( $user->getToken( false ) ) {
+			$this->printDebug( "User has a token, setting domain in user options.", NONSENSITIVE );
+			self::saveDomain( $user, $_SESSION['wsDomain'] );
+		}
+
+		# Let other extensions update the user
+		Hooks::run( 'LDAPUpdateUser', [ &$user ] );
+
+		$this->printDebug( "Saving user settings.", NONSENSITIVE );
+		$user->saveSettings();
+	}
+
+	/**
+	 * When creating a user account, initialize user with information from LDAP.
+	 * TODO: fix setExternalID stuff
+	 *
+	 * @param User &$user
+	 * @param bool $autocreate
+	 */
+	public function initUser( &$user, $autocreate = false ) {
+		$this->printDebug( "Entering initUser", NONSENSITIVE );
+
+		if ( $this->authFailed ) {
+			$this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE );
+			return;
+		}
+		if ( 'local' == $this->getDomain() ) {
+			$this->printDebug( "User is using a local domain", NONSENSITIVE );
+			return;
+		}
+
+		// The update user function does everything else we need done.
+		$this->updateUser( $user );
+
+		// updateUser() won't necessarily save the user's settings
+		$user->saveSettings();
+	}
+
+	/**
+	 * Return true to prevent logins that don't authenticate here from being
+	 * checked against the local database's password fields.
+	 *
+	 * This is just a question, and shouldn't perform any actions.
+	 *
+	 * @return bool
+	 */
+	public function strict() {
+		$this->printDebug( "Entering strict.", NONSENSITIVE );
+
+		if ( $this->getConf( 'UseLocal' ) || $this->getConf( 'MailPassword' ) ) {
+			$this->printDebug( "Returning false in strict().", NONSENSITIVE );
+			return false;
+		}
+		$this->printDebug( "Returning true in strict().", NONSENSITIVE );
+		return true;
+	}
+
+	/**
+	 * Munge the username based on a scheme (lowercase, by default), by search attribute
+	 * otherwise.
+	 *
+	 * @param string $username
+	 * @return string
+	 */
+	public function getCanonicalName( $username ) {
+		global $wgMemc;
+
+		$this->printDebug( "Entering getCanonicalName", NONSENSITIVE );
+		if ( User::isIP( $username ) ) {
+			$this->printDebug( "Username is an IP, not munging.", NONSENSITIVE );
+			return $username;
+		}
+		$key = wfMemcKey( 'ldapauthentication', 'canonicalname', $username );
+		$canonicalname = $username;
+		if ( $username != '' ) {
+			$this->printDebug( "Username is: $username", NONSENSITIVE );
+			if ( $this->getConf( 'LowerCaseUsername' ) ) {
+				$canonicalname = ucfirst( strtolower( $canonicalname ) );
+			} else {
+				# Fetch username, so that we can possibly use it.
+				$userInfo = $wgMemc->get( $key );
+				if ( is_array( $userInfo ) ) {
+					$this->printDebug( "Fetched userInfo from memcache.", NONSENSITIVE );
+					if ( $userInfo["username"] == $username ) {
+						$this->printDebug(
+							"Username matched a key in memcache, using the fetched name: " .
+								$userInfo["canonicalname"],
+							NONSENSITIVE
+						);
+						return $userInfo["canonicalname"];
+					}
+				}
+				if ( $this->validDomain( $this->getDomain() ) && $this->connect() ) {
+					// Try to pull the username from LDAP. In the case of straight binds,
+					// try to fetch the username by search before bind.
+					$this->userdn = $this->getUserDN( $username, true );
+					$hookSetUsername = $this->LDAPUsername;
+					Hooks::run( 'SetUsernameAttributeFromLDAP',
+						[ &$hookSetUsername, $this->userInfo ] );
+					if ( is_string( $hookSetUsername ) ) {
+						$this->printDebug(
+							"Username munged by hook: $hookSetUsername", NONSENSITIVE
+						);
+						$this->LDAPUsername = $hookSetUsername;
+					} else {
+						$this->printDebug(
+							"Fetched username is not a string (check your hook code...). " .
+							"This message can be safely ignored if you do not have the " .
+							"SetUsernameAttributeFromLDAP hook defined.", NONSENSITIVE
+						);
+					}
+				}
+
+				// We want to use the username returned by LDAP
+				// if it exists
+				if ( $this->LDAPUsername != '' ) {
+					$canonicalname = ucfirst( $this->LDAPUsername );
+					$this->printDebug( "Using LDAPUsername: $canonicalname", NONSENSITIVE );
+				}
+
+				$wgMemc->set(
+					$key,
+					[ "username" => $username, "canonicalname" => $canonicalname ],
+					3600 * 24
+				);
+			}
+		}
+		$this->printDebug( "Munged username: $canonicalname", NONSENSITIVE );
+		return $canonicalname;
+	}
+
+	/**
+	 * Configures the authentication plugin for use with auto-authentication
+	 * plugins.
+	 */
+	public function autoAuthSetup() {
+		$this->setDomain( $this->getConf( 'AutoAuthDomain' ) );
+	}
+
+	/**
+	 * Gets the searchstring for a user based upon settings for the domain.
+	 * Returns a full DN for a user.
+	 *
+	 * @param string $username
+	 * @return string
+	 * @access private
+	 */
+	function getSearchString( $username ) {
+		$this->printDebug( "Entering getSearchString", NONSENSITIVE );
+		$ss = $this->getConf( 'SearchString' );
+		if ( $ss ) {
+			// This is a straight bind
+			$this->printDebug( "Doing a straight bind", NONSENSITIVE );
+			$userdn = str_replace( "USER-NAME", $username, $ss );
+		} else {
+			$userdn = $this->getUserDN( $username, true );
+		}
+		$this->printDebug( "userdn is: $userdn", SENSITIVE );
+		return $userdn;
+	}
+
+	/**
+	 * Gets the DN of a user based upon settings for the domain.
+	 * This function will set $this->LDAPUsername
+	 *
+	 * @param string $username
+	 * @param bool $bind
+	 * @param string $searchattr
+	 * @return string
+	 */
+	public function getUserDN( $username, $bind = false, $searchattr = '' ) {
+		$this->printDebug( "Entering getUserDN", NONSENSITIVE );
+		if ( $bind ) {
+			// This is a proxy bind, or an anonymous bind with a search
+			$proxyagent = $this->getConf( 'ProxyAgent' );
+			if ( $proxyagent ) {
+				// This is a proxy bind
+				$this->printDebug( "Doing a proxy bind", NONSENSITIVE );
+				$bind = $this->bindAs( $proxyagent, $this->getConf( 'ProxyAgentPassword' ) );
+			} else {
+				// This is an anonymous bind
+				$this->printDebug( "Doing an anonymous bind", NONSENSITIVE );
+				$bind = $this->bindAs();
+			}
+			if ( !$bind ) {
+				$this->printDebug( "Failed to bind", NONSENSITIVE );
+				$this->fetchedUserInfo = false;
+				$this->userInfo = null;
+				return '';
+			}
+		}
+
+		if ( ! $searchattr ) {
+			$searchattr = $this->getConf( 'SearchAttribute' );
+		}
+		// we need to do a subbase search for the entry
+		$filter = "(" . $searchattr . "=" . $this->getLdapEscapedString( $username ) . ")";
+		$this->printDebug( "Created a regular filter: $filter", SENSITIVE );
+
+		// We explicitly put memberof here because it's an operational attribute in some servers.
+		$attributes = [ "*", "memberof" ];
+		$base = $this->getBaseDN( USERDN );
+		$this->printDebug( "Using base: $base", SENSITIVE );
+		$entry = self::ldap_search(
+			$this->ldapconn, $base, $filter, $attributes
+		);
+		if ( self::ldap_count_entries( $this->ldapconn, $entry ) == 0 ) {
+			$this->printDebug( "Couldn't find an entry", NONSENSITIVE );
+			$this->fetchedUserInfo = false;
+			$this->userInfo = null;
+			return '';
+		}
+		$this->userInfo = self::ldap_get_entries( $this->ldapconn, $entry );
+		$this->fetchedUserInfo = true;
+		if ( isset( $this->userInfo[0][$searchattr] ) ) {
+			$username = $this->userInfo[0][$searchattr][0];
+			$this->printDebug(
+				"Setting the LDAPUsername based on fetched wgLDAPSearchAttributes: $username",
+				NONSENSITIVE
+			);
+			$this->LDAPUsername = $username;
+		}
+		$userdn = $this->userInfo[0]["dn"];
+		return $userdn;
+	}
+
+	/**
+	 * Load the current user's entry
+	 *
+	 * @return bool
+	 */
+	function getUserInfo() {
+		// Don't fetch the same data more than once
+		if ( $this->fetchedUserInfo ) {
+			return true;
+		}
+		$userInfo = $this->getUserInfoStateless( $this->userdn );
+		if ( is_null( $userInfo ) ) {
+			$this->fetchedUserInfo = false;
+		} else {
+			$this->fetchedUserInfo = true;
+			$this->userInfo = $userInfo;
+		}
+		return $this->fetchedUserInfo;
+	}
+
+	/**
+	 * @param string $userdn
+	 * @return array|null
+	 */
+	function getUserInfoStateless( $userdn ) {
+		global $wgMemc;
+
+		$key = wfMemcKey( 'ldapauthentication', 'userinfo', $userdn );
+		$userInfo = $wgMemc->get( $key );
+		if ( !is_array( $userInfo ) ) {
+			$entry = self::ldap_read(
+				$this->ldapconn, $userdn, "objectclass=*", [ '*', 'memberof' ]
+			);
+			$userInfo = self::ldap_get_entries( $this->ldapconn, $entry );
+			if ( $userInfo["count"] < 1 ) {
+				return null;
+			}
+			$wgMemc->set( $key, $userInfo, 3600 * 24 );
+		}
+		return $userInfo;
+	}
+
+	/**
+	 * Retrieve user preferences from LDAP
+	 */
+	private function getPreferences() {
+		$this->printDebug( "Entering getPreferences", NONSENSITIVE );
+
+		// Retrieve preferences
+		$prefs = $this->getConf( 'Preferences' );
+		if ( !$prefs ) {
+			return null;
+		}
+		if ( !$this->getUserInfo() ) {
+			$this->printDebug(
+				"Failed to get preferences, the user's entry wasn't found.", NONSENSITIVE
+			);
+			return null;
+		}
+		$this->printDebug( "Retrieving preferences", NONSENSITIVE );
+		foreach ( array_keys( $prefs ) as $key ) {
+			$attr = strtolower( $prefs[$key] );
+			if ( !isset( $this->userInfo[0][$attr] ) ) {
+				continue;
+			}
+			$value = $this->userInfo[0][$attr][0];
+			switch ( $key ) {
+				case "email":
+					$this->email = $value;
+					$this->printDebug(
+						"Retrieved email ($this->email) using attribute ($prefs[$key])",
+						NONSENSITIVE
+					);
+					break;
+				case "language":
+					$this->lang = $value;
+					$this->printDebug(
+						"Retrieved language ($this->lang) using attribute ($prefs[$key])",
+						NONSENSITIVE
+					);
+					break;
+				case "nickname":
+					$this->nickname = $value;
+					$this->printDebug(
+						"Retrieved nickname ($this->nickname) using attribute ($prefs[$key])",
+						NONSENSITIVE
+					);
+					break;
+				case "realname":
+					$this->realname = $value;
+					$this->printDebug(
+						"Retrieved realname ($this->realname) using attribute ($prefs[$key])",
+						NONSENSITIVE
+					);
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Checks to see whether a user is in a required group.
+	 *
+	 * @return bool
+	 */
+	private function checkGroups() {
+		$this->printDebug( "Entering checkGroups", NONSENSITIVE );
+
+		$excgroups = $this->getConf( 'ExcludedGroups' );
+		if ( $excgroups ) {
+			$this->printDebug( "Checking for excluded group membership", NONSENSITIVE );
+
+			$excgroups = array_map( 'strtolower', $excgroups );
+
+			$this->printDebug( "Excluded groups:", NONSENSITIVE, $excgroups );
+
+			foreach ( $this->userLDAPGroups["dn"] as $group ) {
+				$this->printDebug( "Checking against: $group", NONSENSITIVE );
+				if ( in_array( $group, $excgroups ) ) {
+					$this->printDebug( "Found user in an excluded group.", NONSENSITIVE );
+					return false;
+				}
+			}
+		}
+
+		$reqgroups = $this->getConf( 'RequiredGroups' );
+		if ( $reqgroups ) {
+			$this->printDebug( "Checking for (new style) group membership", NONSENSITIVE );
+
+			$reqgroups = array_map( 'strtolower', $reqgroups );
+
+			$this->printDebug( "Required groups:", NONSENSITIVE, $reqgroups );
+
+			foreach ( $this->userLDAPGroups["dn"] as $group ) {
+				$this->printDebug( "Checking against: $group", NONSENSITIVE );
+				if ( in_array( $group, $reqgroups ) ) {
+					$this->printDebug( "Found user in a group.", NONSENSITIVE );
+					return true;
+				}
+			}
+
+			$this->printDebug( "Couldn't find the user in any groups.", NONSENSITIVE );
+			return false;
+		}
+
+		// Ensure we return true if we aren't checking groups.
+		return true;
+	}
+
+	/**
+	 * Function to get the user's groups.
+	 * @param string $username
+	 */
+	protected function getGroups( $username ) {
+		$this->printDebug( "Entering getGroups", NONSENSITIVE );
+
+		// Ensure userLDAPGroups is set, no matter what
+		$this->userLDAPGroups = [ "dn" => [], "short" => [] ];
+
+		// Find groups
+		if ( $this->getConf( 'RequiredGroups' ) || $this->getConf( 'UseLDAPGroups' ) ) {
+			$this->printDebug( "Retrieving LDAP group membership", NONSENSITIVE );
+
+			// Let's figure out what we should be searching for
+			if ( $this->getConf( 'GroupUseFullDN' ) ) {
+				$usertopass = $this->userdn;
+			} else {
+				if ( $this->getConf( 'GroupUseRetrievedUsername' ) && $this->LDAPUsername != '' ) {
+					$usertopass = $this->LDAPUsername;
+				} else {
+					$usertopass = $username;
+				}
+			}
+
+			if ( $this->getConf( 'GroupsUseMemberOf' ) ) {
+				$this->printDebug( "Using memberOf", NONSENSITIVE );
+				if ( !$this->getUserInfo() ) {
+					$this->printDebug( "Couldn't get the user's entry.", NONSENSITIVE );
+				} elseif ( isset( $this->userInfo[0]["memberof"] ) ) {
+					# The first entry is always a count
+					$memberOfMembers = $this->userInfo[0]["memberof"];
+					array_shift( $memberOfMembers );
+					$groups = [ "dn" => [], "short" => [] ];
+
+					foreach ( $memberOfMembers as $mem ) {
+						array_push( $groups["dn"], strtolower( $mem ) );
+
+						// Get short name of group
+						$memAttrs = explode( ',', strtolower( $mem ) );
+						if ( isset( $memAttrs[0] ) ) {
+							$memAttrs = explode( '=', $memAttrs[0] );
+							if ( isset( $memAttrs[0] ) ) {
+								array_push( $groups["short"], strtolower( $memAttrs[1] ) );
+							}
+						}
+					}
+					$this->printDebug( "Got the following groups:", SENSITIVE, $groups["dn"] );
+
+					$this->userLDAPGroups = $groups;
+				} else {
+					$this->printDebug( "memberOf attribute isn't set", NONSENSITIVE );
+				}
+			} else {
+				$this->printDebug( "Searching for the groups", NONSENSITIVE );
+				$this->userLDAPGroups = $this->searchGroups( $usertopass );
+				if ( $this->getConf( 'GroupSearchNestedGroups' ) ) {
+					$this->userLDAPGroups = $this->searchNestedGroups( $this->userLDAPGroups );
+					$this->printDebug(
+						"Got the following nested groups:", SENSITIVE, $this->userLDAPGroups["dn"]
+					);
+				}
+			}
+
+			if ( $this->getConf( 'GroupSearchPosixPrimaryGroup' ) ) {
+				if ( !$this->getUserInfo() ) {
+					$this->printDebug( "Couldn't get the user's entry.", NONSENSITIVE );
+				} elseif ( isset( $this->userInfo[0]["gidnumber"] ) ) {
+					$base = $this->getBaseDN( GROUPDN );
+					$objectclass = $this->getConf( 'GroupObjectclass' );
+					$filter = "(&(objectClass={$objectclass})" .
+						"(gidNumber={$this->userInfo[0]['gidnumber'][0]}))";
+					$info = self::ldap_search( $this->ldapconn, $base, $filter );
+					$entries = self::ldap_get_entries( $this->ldapconn, $info );
+					if ( empty( $entries[0] ) ) {
+						$this->printDebug( "Couldn't get the user's primary group.", NONSENSITIVE );
+					} else {
+						$primary_group_dn = strtolower( $entries[0]["dn"] );
+						$this->printDebug( "Got the user's primary group:", SENSITIVE, $primary_group_dn );
+						$this->userLDAPGroups["dn"][] = $primary_group_dn;
+						$nameattribute = strtolower( $this->getConf( 'GroupNameAttribute' ) );
+						$this->userLDAPGroups["short"][] = $entries[0][$nameattribute][0];
+					}
+				}
+			}
+
+			// Only find all groups if the user has any groups; otherwise, we are
+			// just wasting a search.
+			if ( $this->getConf( 'GroupsPrevail' ) && count( $this->userLDAPGroups ) != 0 ) {
+				$this->allLDAPGroups = $this->searchGroups( '*' );
+			}
+		}
+	}
+
+	/**
+	 * Function to return an array of nested groups when given a group or list of groups.
+	 * $searchedgroups is used for tail recursion and shouldn't be provided
+	 * when called externally.
+	 *
+	 * @param array $groups
+	 * @param array $searchedgroups
+	 * @return bool
+	 * @access private
+	 */
+	function searchNestedGroups( $groups, $searchedgroups = [ "dn" => [], "short" => [] ] ) {
+		$this->printDebug( "Entering searchNestedGroups", NONSENSITIVE );
+
+		// base case, no more groups left to check
+		if ( count( $groups["dn"] ) == 0 ) {
+			$this->printDebug( "No more groups to search.", NONSENSITIVE );
+			return $searchedgroups;
+		}
+
+		$this->printDebug( "Searching groups:", SENSITIVE, $groups["dn"] );
+		$groupstosearch = [ "short" => [], "dn" => [] ];
+		foreach ( $groups["dn"] as $group ) {
+			$returnedgroups = $this->searchGroups( $group );
+			$this->printDebug(
+				"Group $group is in the following groups:", SENSITIVE, $returnedgroups["dn"]
+			);
+			foreach ( $returnedgroups["dn"] as $searchme ) {
+				if ( in_array( $searchme, $searchedgroups["dn"] ) ) {
+					// We already searched this, move on
+					continue;
+				} else {
+					// We'll need to search this group's members now
+					$this->printDebug( "Adding $searchme to the list of groups (1)", SENSITIVE );
+					$groupstosearch["dn"][] = $searchme;
+				}
+			}
+			foreach ( $returnedgroups["short"] as $searchme ) {
+				if ( in_array( $searchme, $searchedgroups["short"] ) ) {
+					// We already searched this, move on
+					continue;
+				} else {
+					$this->printDebug( "Adding $searchme to the list of groups (2)", SENSITIVE );
+					// We'll need to search this group's members now
+					$groupstosearch["short"][] = $searchme;
+				}
+			}
+		}
+		$searchedgroups = array_merge_recursive( $groups, $searchedgroups );
+
+		return $this->searchNestedGroups( $groupstosearch, $searchedgroups );
+	}
+
+	/**
+	 * Search groups for the supplied DN
+	 *
+	 * @param string $dn
+	 * @return array
+	 */
+	private function searchGroups( $dn ) {
+		$this->printDebug( "Entering searchGroups", NONSENSITIVE );
+
+		$base = $this->getBaseDN( GROUPDN );
+		$objectclass = $this->getConf( 'GroupObjectclass' );
+		$attribute = $this->getConf( 'GroupAttribute' );
+		$nameattribute = $this->getConf( 'GroupNameAttribute' );
+
+		// We actually want to search for * not \2a, ensure we don't escape *
+		$value = $dn;
+		if ( $value != "*" ) {
+			$value = $this->getLdapEscapedString( $value );
+		}
+
+		$proxyagent = $this->getConf( 'ProxyAgent' );
+		if ( $proxyagent ) {
+			// We'll try to bind as the proxyagent as the proxyagent should normally have more
+			// rights than the user. If the proxyagent fails to bind, we will still be able
+			// to search as the normal user (which is why we don't return on fail).
+			$this->printDebug( "Binding as the proxyagent", NONSENSITIVE );
+			$this->bindAs( $proxyagent, $this->getConf( 'ProxyAgentPassword' ) );
+		}
+
+		$groups = [ "short" => [], "dn" => [] ];
+
+		// AD does not include the primary group in the list of groups, we have to find it ourselves
+		if ( $dn != "*" && $this->getConf( 'ActiveDirectory' ) ) {
+			$PGfilter = "(&(distinguishedName=$value)(objectclass=user))";
+			$this->printDebug( "User Filter: $PGfilter", SENSITIVE );
+			$PGinfo = self::ldap_search( $this->ldapconn, $base, $PGfilter );
+			$PGentries = self::ldap_get_entries( $this->ldapconn, $PGinfo );
+			if ( !empty( $PGentries[0] ) ) {
+				$Usid = $PGentries[0]['objectsid'][0];
+				$PGrid = $PGentries[0]['primarygroupid'][0];
+				$PGsid = bin2hex( $Usid );
+				$PGSID = [];
+				for ( $i = 0; $i < 56; $i += 2 ) {
+					$PGSID[] = substr( $PGsid, $i, 2 );
+				}
+				$dPGrid = dechex( $PGrid );
+				$dPGrid = str_pad( $dPGrid, 8, '0', STR_PAD_LEFT );
+				$PGRID = [];
+				for ( $i = 0; $i < 8; $i += 2 ) {
+					array_push( $PGRID, substr( $dPGrid, $i, 2 ) );
+				}
+				for ( $i = 24; $i < 28; $i++ ) {
+					$PGSID[$i] = array_pop( $PGRID );
+				}
+				$PGsid_string = '';
+				foreach ( $PGSID as $PGsid_bit ) {
+					$PGsid_string .= "\\" . $PGsid_bit;
+				}
+				$PGfilter = "(&(objectSid=$PGsid_string)(objectclass=$objectclass))";
+				$this->printDebug( "Primary Group Filter: $PGfilter", SENSITIVE );
+				$info = self::ldap_search( $this->ldapconn, $base, $PGfilter );
+				$PGentries = self::ldap_get_entries( $this->ldapconn, $info );
+				array_shift( $PGentries );
+				$dnMember = strtolower( $PGentries[0]['dn'] );
+				$groups["dn"][] = $dnMember;
+				// Get short name of group
+				$memAttrs = explode( ',', strtolower( $dnMember ) );
+				if ( isset( $memAttrs[0] ) ) {
+					$memAttrs = explode( '=', $memAttrs[0] );
+					if ( isset( $memAttrs[1] ) ) {
+						$groups["short"][] = strtolower( $memAttrs[1] );
+					}
+				}
+
+			}
+		}
+
+		$filter = "(&($attribute=$value)(objectclass=$objectclass))";
+		$this->printDebug( "Search string: $filter", SENSITIVE );
+		$info = self::ldap_search( $this->ldapconn, $base, $filter );
+		if ( !$info ) {
+			$this->printDebug( "No entries returned from search.", SENSITIVE );
+			// Return an array so that other functions
+			// don't error out.
+			return [ "short" => [], "dn" => [] ];
+		}
+
+		$entries = self::ldap_get_entries( $this->ldapconn, $info );
+		if ( $entries ) {
+			// We need to shift because the first entry will be a count
+			array_shift( $entries );
+			// Let's get a list of both full dn groups and shortname groups
+			foreach ( $entries as $entry ) {
+				$shortMember = strtolower( $entry[$nameattribute][0] );
+				$dnMember = strtolower( $entry['dn'] );
+				$groups["short"][] = $shortMember;
+				$groups["dn"][] = $dnMember;
+			}
+		}
+
+		$this->printDebug( "Returned groups:", SENSITIVE, $groups["dn"] );
+		return $groups;
+	}
+
+	/**
+	 * Returns true if this group is in the list of the currently authenticated
+	 * user's groups, else false.
+	 *
+	 * @param string $group
+	 * @return bool
+	 * @access private
+	 */
+	function hasLDAPGroup( $group ) {
+		$this->printDebug( "Entering hasLDAPGroup", NONSENSITIVE );
+		return in_array( strtolower( $group ), $this->userLDAPGroups["short"] );
+	}
+
+	/**
+	 * Returns true if an LDAP group with this name exists, else false.
+	 *
+	 * @param string $group
+	 * @return bool
+	 * @access private
+	 */
+	function isLDAPGroup( $group ) {
+		$this->printDebug( "Entering isLDAPGroup", NONSENSITIVE );
+		return in_array( strtolower( $group ), $this->allLDAPGroups["short"] );
+	}
+
+	/**
+	 * Helper function for updateUser() and initUser(). Adds users into MediaWiki security groups
+	 * based upon groups retreived from LDAP.
+	 *
+	 * @param User &$user
+	 * @access private
+	 */
+	function setGroups( &$user ) {
+		global $wgGroupPermissions;
+
+		// TODO: this is *really* ugly code. clean it up!
+		$this->printDebug( "Entering setGroups.", NONSENSITIVE );
+
+		# Add ldap groups as local groups
+		if ( $this->getConf( 'GroupsPrevail' ) ) {
+			$this->printDebug(
+				"Adding all groups to wgGroupPermissions: ", SENSITIVE, $this->allLDAPGroups
+			);
+
+			foreach ( $this->allLDAPGroups["short"] as $ldapgroup ) {
+				if ( !array_key_exists( $ldapgroup, $wgGroupPermissions ) ) {
+					$wgGroupPermissions[$ldapgroup] = [];
+				}
+			}
+		}
+
+		# add groups permissions
+		$localAvailGrps = $user->getAllGroups();
+		$localUserGrps = $user->getEffectiveGroups();
+		$defaultLocallyManagedGrps = [ 'bot', 'sysop', 'bureaucrat' ];
+		$locallyManagedGrps = $this->getConf( 'LocallyManagedGroups' );
+		if ( $locallyManagedGrps ) {
+			$locallyManagedGrps = array_unique(
+				array_merge( $defaultLocallyManagedGrps, $locallyManagedGrps ) );
+			$this->printDebug( "Locally managed groups: ", SENSITIVE, $locallyManagedGrps );
+		} else {
+			$locallyManagedGrps = $defaultLocallyManagedGrps;
+			$this->printDebug(
+				"Locally managed groups is unset, using defaults: ", SENSITIVE, $locallyManagedGrps
+			);
+		}
+
+		$this->printDebug( "Available groups are: ", NONSENSITIVE, $localAvailGrps );
+		$this->printDebug( "Effective groups are: ", NONSENSITIVE, $localUserGrps );
+		# note: $localUserGrps does not need to be updated with $cGroup added,
+		#       as $localAvailGrps contains $cGroup only once.
+		foreach ( $localAvailGrps as $cGroup ) {
+			# did we once add the user to the group?
+			if ( in_array( $cGroup, $localUserGrps ) ) {
+				$this->printDebug(
+					"Checking to see if we need to remove user from: $cGroup", NONSENSITIVE
+				);
+				if ( !$this->hasLDAPGroup( $cGroup ) &&
+					!in_array( $cGroup, $locallyManagedGrps )
+				) {
+					$this->printDebug( "Removing user from: $cGroup", NONSENSITIVE );
+					# the ldap group overrides the local group
+					# so as the user is currently not a member of the ldap group,
+					# he shall be removed from the local group
+					$user->removeGroup( $cGroup );
+				}
+			} else {
+				# no, but maybe the user has recently been added to the ldap group?
+				$this->printDebug( "Checking to see if user is in: $cGroup", NONSENSITIVE );
+				if ( $this->hasLDAPGroup( $cGroup ) ) {
+					$this->printDebug( "Adding user to: $cGroup", NONSENSITIVE );
+					$user->addGroup( $cGroup );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns a password that is created via the configured hash settings.
+	 *
+	 * @param string $password
+	 * @return string
+	 * @access private
+	 */
+	function getPasswordHash( $password ) {
+		$this->printDebug( "Entering getPasswordHash", NONSENSITIVE );
+
+		// Set the password hashing based upon admin preference
+		switch ( $this->getConf( 'PasswordHash' ) ) {
+			case 'crypt':
+				// https://bugs.php.net/bug.php?id=55439
+				if ( crypt( 'password', '$1$U7AjYB.O$' ) == '$1$U7AjYB.O' ) {
+					die( 'The version of PHP in use has a broken crypt function. Please upgrade ' .
+						'your installation of PHP, or use another encryption function for LDAP.' );
+				}
+				$pass = '{CRYPT}' . crypt( $password );
+				break;
+			case 'clear':
+				$pass = $password;
+				break;
+			default:
+				$pwd_sha = base64_encode( pack( 'H*', sha1( $password ) ) );
+				$pass = "{SHA}" . $pwd_sha;
+				break;
+		}
+
+		return $pass;
+	}
+
+	/**
+	 * Prints debugging information. $debugText is what you want to print, $debugVal
+	 * is the level at which you want to print the information.
+	 *
+	 * @param string $debugText
+	 * @param string $debugVal
+	 * @param Array|null $debugArr
+	 * @access private
+	 */
+	function printDebug( $debugText, $debugVal, $debugArr = null ) {
+		if ( !function_exists( 'wfDebugLog' ) ) {
+			return;
+		}
+
+		global $wgLDAPDebug;
+
+		if ( $wgLDAPDebug >= $debugVal ) {
+			if ( isset( $debugArr ) ) {
+				$debugText = $debugText . " " . implode( "::", $debugArr );
+			}
+			wfDebugLog( 'ldap', LDAPAUTHVERSION . ' ' . $debugText, false );
+		}
+	}
+
+	/**
+	 * Binds as $userdn with $password. This can be called with only the ldap
+	 * connection resource for an anonymous bind.
+	 *
+	 * @param string $userdn
+	 * @param string $password
+	 * @return bool
+	 * @access private
+	 */
+	function bindAs( $userdn = null, $password = null ) {
+		// Let's see if the user can authenticate.
+		if ( $userdn == null || $password == null ) {
+			$bind = self::ldap_bind( $this->ldapconn );
+		} else {
+			$bind = self::ldap_bind( $this->ldapconn, $userdn, $password );
+		}
+		if ( !$bind ) {
+			$this->printDebug( "Failed to bind as $userdn", NONSENSITIVE );
+			return false;
+		}
+		$this->boundAs = $userdn;
+		return true;
+	}
+
+	/**
+	 * Returns true if auto-authentication is allowed, and the user is
+	 * authenticating using the auto-authentication domain.
+	 *
+	 * @return bool
+	 * @access private
+	 */
+	function useAutoAuth() {
+		return $this->getDomain() == $this->getConf( 'AutoAuthDomain' );
+	}
+
+	/**
+	 * Returns a string which has the chars *, (, ), \ & NUL escaped to LDAP compliant
+	 * syntax as per RFC 2254
+	 * Thanks and credit to Iain Colledge for the research and function.
+	 *
+	 * @param string $string
+	 * @return string
+	 * @access private
+	 */
+	function getLdapEscapedString( $string ) {
+		// Make the string LDAP compliant by escaping *, (, ) , \ & NUL
+		return str_replace(
+			[ "\\", "(", ")", "*", "\x00" ],
+			[ "\\5c", "\\28", "\\29", "\\2a", "\\00" ],
+			$string
+			);
+	}
+
+	/**
+	 * Returns a basedn by the type of entry we are searching for.
+	 *
+	 * @param int $type
+	 * @return string
+	 * @access private
+	 */
+	function getBaseDN( $type ) {
+		$this->printDebug( "Entering getBaseDN", NONSENSITIVE );
+
+		$ret = '';
+		switch ( $type ) {
+			case USERDN:
+				$ret = $this->getConf( 'UserBaseDN' );
+				break;
+			case GROUPDN:
+				$ret = $this->getConf( 'GroupBaseDN' );
+				break;
+			case DEFAULTDN:
+				$ret = $this->getConf( 'BaseDN' );
+				if ( $ret ) {
+					return $ret;
+				} else {
+					$this->printDebug( "basedn is not set.", NONSENSITIVE );
+					return '';
+				}
+		}
+
+		if ( $ret == '' ) {
+			$this->printDebug(
+				"basedn is not set for this type of entry, trying to get the default basedn.",
+				NONSENSITIVE
+			);
+			// We will never reach here if $type is self::DEFAULTDN, so to avoid code
+			// code duplication, we'll get the default by re-calling the function.
+			return $this->getBaseDN( DEFAULTDN );
+		} else {
+			$this->printDebug( "basedn is $ret", NONSENSITIVE );
+			return $ret;
+		}
+	}
+
+	/**
+	 * @param User $user
+	 * @return string
+	 */
+	static function loadDomain( $user ) {
+		$user_id = $user->getId();
+		if ( $user_id != 0 ) {
+			$dbr = wfGetDB( DB_REPLICA );
+			$row = $dbr->selectRow(
+				'ldap_domains',
+				[ 'domain' ],
+				[ 'user_id' => $user_id ],
+				__METHOD__ );
+
+			if ( $row ) {
+				return $row->domain;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * @param User $user
+	 * @param string $domain
+	 * @return bool
+	 */
+	static function saveDomain( $user, $domain ) {
+		$user_id = $user->getId();
+		if ( $user_id != 0 ) {
+			$dbw = wfGetDB( DB_MASTER );
+			$olddomain = self::loadDomain( $user );
+			if ( $olddomain ) {
+				return $dbw->update(
+					'ldap_domains',
+					[ 'domain' => $domain ],
+					[ 'user_id' => $user_id ],
+					__METHOD__
+				);
+			} else {
+				return $dbw->insert(
+					'ldap_domains',
+					[
+						'domain' => $domain,
+						'user_id' => $user_id
+					],
+					__METHOD__
+				);
+			}
+		}
+		return false;
+	}
+
+}
diff --git a/extensions/LdapAuthentication/LdapAutoAuthentication.php b/extensions/LdapAuthentication/LdapAutoAuthentication.php
new file mode 100644
index 00000000..8eab5769
--- /dev/null
+++ b/extensions/LdapAuthentication/LdapAutoAuthentication.php
@@ -0,0 +1,118 @@
+<?php
+
+class LdapAutoAuthentication {
+
+	/**
+	 * Does the web server authentication piece of the LDAP plugin.
+	 *
+	 * @param User $user
+	 * @param bool &$result
+	 * @return bool
+	 */
+	public static function Authenticate( $user, &$result = null ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+
+		$ldap->printDebug( "Entering AutoAuthentication.", NONSENSITIVE );
+
+		if ( $user->isLoggedIn() ) {
+			$ldap->printDebug( "User is already logged in.", NONSENSITIVE );
+			return true;
+		}
+
+		$ldap->printDebug( "User isn't logged in, calling setup.", NONSENSITIVE );
+
+		// Let regular authentication plugins configure themselves for auto
+		// authentication chaining
+		$ldap->autoAuthSetup();
+
+		$autoauthname = $ldap->getConf( 'AutoAuthUsername' );
+		$ldap->printDebug( "Calling authenticate with username ($autoauthname).", NONSENSITIVE );
+
+		// The user hasn't already been authenticated, let's check them
+		$authenticated = $ldap->authenticate( $autoauthname, '' );
+		if ( !$authenticated ) {
+			// If the user doesn't exist in LDAP, there isn't much reason to
+			// go any further.
+			$ldap->printDebug( "User wasn't found in LDAP, exiting.", NONSENSITIVE );
+			return false;
+		}
+
+		// We need the username that MediaWiki will always use, not necessarily the one we
+		// get from LDAP.
+		$mungedUsername = $ldap->getCanonicalName( $autoauthname );
+
+		$ldap->printDebug(
+			"User exists in LDAP; finding the user by name ($mungedUsername) in MediaWiki.",
+			NONSENSITIVE
+		);
+		$localId = User::idFromName( $mungedUsername );
+		$ldap->printDebug( "Got id ($localId).", NONSENSITIVE );
+
+		// Is the user already in the database?
+		if ( !$localId ) {
+			$userAdded = self::attemptAddUser( $user, $mungedUsername );
+			if ( !$userAdded ) {
+				$result = false;
+				return false;
+			}
+		} else {
+			$ldap->printDebug( "User exists in local database, logging in.", NONSENSITIVE );
+			$user->setID( $localId );
+			$user->loadFromId();
+			$user->setCookies();
+			$ldap->updateUser( $user );
+			wfSetupSession();
+			$result = true;
+		}
+
+		return true;
+	}
+
+	/**
+	 * @param User $user
+	 * @param string $mungedUsername
+	 * @return bool
+	 */
+	public static function attemptAddUser( $user, $mungedUsername ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+
+		if ( !$ldap->autoCreate() ) {
+			$ldap->printDebug( "Cannot automatically create accounts.", NONSENSITIVE );
+			return false;
+		}
+
+		$ldap->printDebug( "User does not exist in local database; creating.", NONSENSITIVE );
+		// Checks passed, create the user
+		$user->loadDefaults( $mungedUsername );
+		$status = $user->addToDatabase();
+		if ( $status !== null && !$status->isOK() ) {
+			$ldap->printDebug( "Creation failed: " . $status->getWikiText(), NONSENSITIVE );
+			return false;
+		}
+		$ldap->initUser( $user, true );
+		$user->setCookies();
+		wfSetupSession();
+		# Update user count
+		$ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
+		$ssUpdate->doUpdate();
+		# Notify hooks (e.g. Newuserlog)
+		Hooks::run( 'AuthPluginAutoCreate', [ $user ] );
+
+		return true;
+	}
+
+	/**
+	 * No logout link in MW
+	 * @param array &$personal_urls
+	 * @param Title $title
+	 * @return bool
+	 */
+	public static function NoLogout( &$personal_urls, $title ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+
+		$ldap->printDebug( "Entering NoLogout.", NONSENSITIVE );
+		unset( $personal_urls['logout'] );
+		return true;
+	}
+
+}
diff --git a/extensions/LdapAuthentication/LdapPrimaryAuthenticationProvider.php b/extensions/LdapAuthentication/LdapPrimaryAuthenticationProvider.php
new file mode 100644
index 00000000..2a97fdb8
--- /dev/null
+++ b/extensions/LdapAuthentication/LdapPrimaryAuthenticationProvider.php
@@ -0,0 +1,444 @@
+<?php
+/**
+ * Primary authentication provider wrapper for LdapAuthentication
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\PasswordAuthenticationRequest;
+use MediaWiki\Auth\PasswordDomainAuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use Wikimedia\ScopedCallback;
+
+/**
+ * Primary authentication provider wrapper for LdapAuthentication
+ * @warning This is rather hacky, and probably doesn't fully support what
+ *  LdapAuthenticationPlugin can do. In particular, it's probably even more
+ *  likely to have bugs with multiple domains than LdapAuthenticationPlugin
+ *  itself, since AuthManager may well break some of the assumptions that
+ *  LdapAuthenticationPlugin makes based on the old AuthPlugin-using code, and
+ *  it doesn't support any username munging (i.e.
+ *  LdapAuthenticationPlugin::getCanonicalName()).
+ * @todo Someday someone who knows about authenticating against LDAP should
+ *  write an extension that doesn't have craziness like a global "domain"
+ *  variable where we have to guess at the correct value in half the entry
+ *  points.
+ */
+class LdapPrimaryAuthenticationProvider
+	extends AbstractPasswordPrimaryAuthenticationProvider
+{
+	private $hasMultipleDomains;
+	private $requestType;
+
+	public function __construct() {
+		parent::__construct();
+
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$this->hasMultipleDomains = count( $ldap->domainList() ) > 1;
+		$this->requestType = $this->hasMultipleDomains
+			? PasswordDomainAuthenticationRequest::class
+			: PasswordAuthenticationRequest::class;
+
+		// Hooks to handle updating LDAP on various core events
+		\Hooks::register( 'UserSaveSettings', [ $this, 'onUserSaveSettings' ] );
+		\Hooks::register( 'UserGroupsChanged', [ $this, 'onUserGroupsChanged' ] );
+		\Hooks::register( 'UserLoggedIn', [ $this, 'onUserLoggedIn' ] );
+		\Hooks::register( 'LocalUserCreated', [ $this, 'onLocalUserCreated' ] );
+	}
+
+	/**
+	 * Sets the "domain" to that for the specified user, if any
+	 * @param LdapAuthenticationPlugin $ldap
+	 * @param User $user
+	 * @return ScopedCallback|null Resetter
+	 */
+	private function setDomainForUser( LdapAuthenticationPlugin $ldap, User $user ) {
+		if ( !$this->hasMultipleDomains ) {
+			// LdapAuthenticationPlugin still needs setDomain called, even if
+			// getDomain is deterministic. Sigh.
+			$ldap->setDomain( $ldap->getDomain() );
+			return null;
+		}
+
+		$domain = LdapAuthenticationPlugin::loadDomain( $user );
+		if ( $domain === null ) {
+			return null;
+		}
+
+		$oldDomain = $ldap->getDomain();
+		if ( $domain === $oldDomain ) {
+			// No change, so no point
+			return null;
+		}
+
+		$ldap->setDomain( $domain );
+		return new ScopedCallback( [ $ldap, 'setDomain' ], [ $oldDomain ] );
+	}
+
+	/**
+	 * Create an appropriate AuthenticationRequest
+	 * @return PasswordAuthenticationRequest
+	 */
+	protected function makeAuthReq() {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$domainList = $ldap->domainList();
+		if ( count( $domainList ) > 1 ) {
+			return new PasswordDomainAuthenticationRequest( $domainList );
+		} else {
+			return new PasswordAuthenticationRequest;
+		}
+	}
+
+	/**
+	 * Hook function to call LdapAuthenticationPlugin::updateExternalDB()
+	 * @param User $user
+	 * @codeCoverageIgnore
+	 */
+	public function onUserSaveSettings( $user ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$reset = $this->setDomainForUser( $ldap, $user );
+		$ldap->updateExternalDB( $user );
+		ScopedCallback::consume( $reset );
+	}
+
+	/**
+	 * Hook function to call LdapAuthenticationPlugin::updateExternalDBGroups()
+	 * @param User $user
+	 * @param array $added
+	 * @param array $removed
+	 */
+	public function onUserGroupsChanged( $user, $added, $removed ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$reset = $this->setDomainForUser( $ldap, $user );
+		$ldap->updateExternalDBGroups( $user, $added, $removed );
+		ScopedCallback::consume( $reset );
+	}
+
+	/**
+	 * Hook function to call LdapAuthenticationPlugin::updateUser()
+	 * @param User $user
+	 */
+	public function onUserLoggedIn( $user ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$reset = $this->setDomainForUser( $ldap, $user );
+		$ldap->updateUser( $user );
+		ScopedCallback::consume( $reset );
+	}
+
+	/**
+	 * Hook function to call LdapAuthenticationPlugin::initUser()
+	 * @param User $user
+	 * @param bool $autocreated
+	 */
+	public function onLocalUserCreated( $user, $autocreated ) {
+		// For $autocreated, see self::autoCreatedAccount()
+		if ( !$autocreated ) {
+			$ldap = LdapAuthenticationPlugin::getInstance();
+			$reset = $this->setDomainForUser( $ldap, $user );
+			$ldap->initUser( $user, $autocreated );
+			ScopedCallback::consume( $reset );
+		}
+	}
+
+	public function getAuthenticationRequests( $action, array $options ) {
+		switch ( $action ) {
+			case AuthManager::ACTION_LOGIN:
+			case AuthManager::ACTION_CREATE:
+				return [ $this->makeAuthReq() ];
+
+			case AuthManager::ACTION_CHANGE:
+			case AuthManager::ACTION_REMOVE:
+				// No way to know the domain here.
+				$ldap = LdapAuthenticationPlugin::getInstance();
+				return $ldap->allowPasswordChange() ? [ $this->makeAuthReq() ] : [];
+
+			default:
+				return [];
+		}
+	}
+
+	public function beginPrimaryAuthentication( array $reqs ) {
+		$req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType );
+		if ( !$req || $req->username === null || $req->password === null ||
+			( $this->hasMultipleDomains && $req->domain === null )
+		) {
+			return AuthenticationResponse::newAbstain();
+		}
+
+		$username = User::getCanonicalName( $req->username, 'usable' );
+		if ( $username === false ) {
+			return AuthenticationResponse::newAbstain();
+		}
+
+		$ldap = LdapAuthenticationPlugin::getInstance();
+
+		if ( $this->hasMultipleDomains ) {
+			// Special:UserLogin does this. Strange.
+			$domain = $req->domain;
+			if ( !$ldap->validDomain( $domain ) ) {
+				$domain = $ldap->getDomain();
+			}
+		} else {
+			$domain = $ldap->getDomain();
+		}
+		$ldap->setDomain( $domain );
+
+		if ( $this->testUserCanAuthenticateInternal( $ldap, User::newFromName( $username ) ) &&
+			$ldap->authenticate( $username, $req->password )
+		) {
+			return AuthenticationResponse::newPass( $username );
+		} else {
+			$this->authoritative = $ldap->strict() || $ldap->strictUserAuth( $username );
+			return $this->failResponse( $req );
+		}
+	}
+
+	public function testUserCanAuthenticate( $username ) {
+		$username = User::getCanonicalName( $username, 'usable' );
+		if ( $username === false ) {
+			return false;
+		}
+
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		if ( $this->hasMultipleDomains ) {
+			// We have to check every domain to really determine if the user can authenticate
+			$curDomain = $ldap->getDomain();
+			foreach ( $ldap->domainList() as $domain ) {
+				$ldap->setDomain( $domain );
+				if ( $this->testUserCanAuthenticateInternal( $ldap, User::newFromName( $username ) ) ) {
+					$ldap->setDomain( $curDomain );
+					return true;
+				}
+			}
+			$ldap->setDomain( $curDomain );
+			return false;
+		} else {
+			// Yay, easy way out.
+			$ldap->setDomain( $ldap->getDomain() );
+			return $this->testUserCanAuthenticateInternal( $ldap, User::newFromName( $username ) );
+		}
+	}
+
+	/**
+	 * Test if a user can authenticate against $ldap's current domain
+	 * @param LdapAuthenticationPlugin $ldap
+	 * @param User $user
+	 * @return bool
+	 */
+	private function testUserCanAuthenticateInternal( LdapAuthenticationPlugin $ldap, $user ) {
+		if ( $ldap->userExistsReal( $user->getName() ) ) {
+			return !$ldap->getUserInstance( $user )->isLocked();
+		} else {
+			return false;
+		}
+	}
+
+	public function providerRevokeAccessForUser( $username ) {
+		$username = User::getCanonicalName( $username, 'usable' );
+		if ( $username === false ) {
+			return;
+		}
+		$user = User::newFromName( $username );
+		if ( $user ) {
+			// Reset the password on every domain.
+			$ldap = LdapAuthenticationPlugin::getInstance();
+			$curDomain = $ldap->getDomain();
+			$domains = $ldap->domainList() ?: [ '(default)' ];
+			$failed = [];
+			foreach ( $domains as $domain ) {
+				$ldap->setDomain( $domain );
+				if ( $ldap->userExistsReal( $username ) &&
+					!$ldap->setPassword( $user, null )
+				) {
+					$failed[] = $domain;
+				}
+			}
+			$ldap->setDomain( $curDomain );
+			if ( $failed ) {
+				throw new \UnexpectedValueException(
+					"LdapAuthenticationPlugin failed to reset password for $username in the following domains: "
+						. implode( ' ', $failed )
+				);
+			}
+		}
+	}
+
+	public function testUserExists( $username, $flags = User::READ_NORMAL ) {
+		$username = User::getCanonicalName( $username, 'usable' );
+		if ( $username === false ) {
+			return false;
+		}
+
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		if ( $this->hasMultipleDomains ) {
+			// We have to check every domain to really determine if the user can authenticate
+			$curDomain = $ldap->getDomain();
+			foreach ( $ldap->domainList() as $domain ) {
+				$ldap->setDomain( $domain );
+				if ( $ldap->userExistsReal( $username ) ) {
+					$ldap->setDomain( $curDomain );
+					return true;
+				}
+			}
+			$ldap->setDomain( $curDomain );
+			return false;
+		} else {
+			// Yay, easy way out.
+			$ldap->setDomain( $ldap->getDomain() );
+			return $ldap->userExistsReal( $username );
+		}
+	}
+
+	public function providerAllowsPropertyChange( $property ) {
+		// No way to know the right domain to query.
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		return $ldap->allowPropChange( $property );
+	}
+
+	public function providerAllowsAuthenticationDataChange(
+		AuthenticationRequest $req, $checkData = true
+	) {
+		if ( get_class( $req ) !== $this->requestType ) {
+			return \StatusValue::newGood( 'ignored' );
+		}
+
+		if ( $this->hasMultipleDomains && $req->domain === 'local' ) {
+			return \StatusValue::newGood( 'ignored' );
+		}
+
+		$ldap = LdapAuthenticationPlugin::getInstance();
+
+		$curDomain = $ldap->getDomain();
+		if ( $checkData ) {
+			$ldap->setDomain( $this->hasMultipleDomains ? $req->domain : $curDomain );
+		}
+		try {
+			// If !$checkData the domain might be wrong. Nothing we can do about that.
+			if ( !$ldap->allowPasswordChange() || !$ldap->getConf( 'WriterDN' ) ) {
+				return \StatusValue::newFatal( 'authmanager-authplugin-setpass-denied' );
+			}
+
+			if ( !$checkData ) {
+				return \StatusValue::newGood();
+			}
+
+			if ( $this->hasMultipleDomains ) {
+				if ( $req->domain === null ) {
+					return \StatusValue::newGood( 'ignored' );
+				}
+				if ( !$ldap->validDomain( $req->domain ) ) {
+					return \StatusValue::newFatal( 'authmanager-authplugin-setpass-bad-domain' );
+				}
+			}
+
+			$username = User::getCanonicalName( $req->username, 'usable' );
+			if ( $username !== false ) {
+				$sv = \StatusValue::newGood();
+				if ( $req->password !== null ) {
+					if ( $req->password !== $req->retype ) {
+						$sv->fatal( 'badretype' );
+					} else {
+						$sv->merge( $this->checkPasswordValidity( $username, $req->password )->getStatusValue() );
+					}
+				}
+				return $sv;
+			} else {
+				return \StatusValue::newGood( 'ignored' );
+			}
+		} finally {
+			$ldap->setDomain( $curDomain );
+		}
+	}
+
+	public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
+		if ( get_class( $req ) === $this->requestType ) {
+			$username = $req->username !== null ? User::getCanonicalName( $req->username, 'usable' ) : false;
+			if ( $username === false ) {
+				return;
+			}
+
+			if ( $this->hasMultipleDomains && $req->domain === null ) {
+				return;
+			}
+
+			$ldap = LdapAuthenticationPlugin::getInstance();
+			$ldap->setDomain( $this->hasMultipleDomains ? $req->domain : $ldap->getDomain() );
+			$user = User::newFromName( $username );
+			if ( !$ldap->setPassword( $user, $req->password ) ) {
+				// This is totally unfriendly and leaves other
+				// AuthenticationProviders in an uncertain state, but what else
+				// can we do?
+				throw new \ErrorPageError(
+					'authmanager-authplugin-setpass-failed-title',
+					'authmanager-authplugin-setpass-failed-message'
+				);
+			}
+		}
+	}
+
+	public function accountCreationType() {
+		// No way to know the domain, just hope it works
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		return $ldap->canCreateAccounts() ? self::TYPE_CREATE : self::TYPE_NONE;
+	}
+
+	public function testForAccountCreation( $user, $creator, array $reqs ) {
+		return \StatusValue::newGood();
+	}
+
+	public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
+		if ( $this->accountCreationType() === self::TYPE_NONE ) {
+			throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
+		}
+
+		$req = AuthenticationRequest::getRequestByClass( $reqs, $this->requestType );
+		if ( !$req || $req->username === null || $req->password === null ||
+			( $this->hasMultipleDomains && $req->domain === null )
+		) {
+			return AuthenticationResponse::newAbstain();
+		}
+
+		$username = User::getCanonicalName( $req->username, 'usable' );
+		if ( $username === false ) {
+			return AuthenticationResponse::newAbstain();
+		}
+
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$ldap->setDomain( $this->hasMultipleDomains ? $req->domain : $ldap->getDomain() );
+		if ( $ldap->addUser(
+			$user, $req->password, $user->getEmail(), $user->getRealName()
+		) ) {
+			return AuthenticationResponse::newPass();
+		} else {
+			return AuthenticationResponse::newFail(
+				new \Message( 'authmanager-authplugin-create-fail' )
+			);
+		}
+	}
+
+	public function autoCreatedAccount( $user, $source ) {
+		$ldap = LdapAuthenticationPlugin::getInstance();
+		$reset = $this->setDomainForUser( $ldap, $user );
+		$ldap->initUser( $user, true );
+		ScopedCallback::consume( $reset );
+	}
+}
diff --git a/extensions/LdapAuthentication/README b/extensions/LdapAuthentication/README
new file mode 100644
index 00000000..72751b29
--- /dev/null
+++ b/extensions/LdapAuthentication/README
@@ -0,0 +1 @@
+This authentication plugin allows MediaWiki to use an LDAP store as its user database for authentication, and some authorization. Full functionality and configuration information can be found at: https://www.mediawiki.org/wiki/Extension:LDAP_Authentication
diff --git a/extensions/LdapAuthentication/composer.json b/extensions/LdapAuthentication/composer.json
new file mode 100644
index 00000000..544e44a8
--- /dev/null
+++ b/extensions/LdapAuthentication/composer.json
@@ -0,0 +1,19 @@
+{
+	"require-dev": {
+		"jakub-onderka/php-parallel-lint": "1.0.0",
+		"jakub-onderka/php-console-highlighter": "0.3.2",
+		"mediawiki/mediawiki-codesniffer": "18.0.0",
+		"mediawiki/minus-x": "0.3.1"
+	},
+	"scripts": {
+		"fix": [
+			"phpcbf",
+			"minus-x fix ."
+		],
+		"test": [
+			"parallel-lint . --exclude vendor --exclude node_modules",
+			"phpcs -p -s",
+			"minus-x check ."
+		]
+	}
+}
diff --git a/extensions/LdapAuthentication/gitinfo.json b/extensions/LdapAuthentication/gitinfo.json
new file mode 100644
index 00000000..72a4beab
--- /dev/null
+++ b/extensions/LdapAuthentication/gitinfo.json
@@ -0,0 +1 @@
+{"headSHA1": "b19888c34a46a2e5838112850032815852661694\n", "head": "b19888c34a46a2e5838112850032815852661694\n", "remoteURL": "https://gerrit.wikimedia.org/r/mediawiki/extensions/LdapAuthentication", "branch": "b19888c34a46a2e5838112850032815852661694\n", "headCommitDate": "1523675488"}
\ No newline at end of file
diff --git a/extensions/LdapAuthentication/i18n/af.json b/extensions/LdapAuthentication/i18n/af.json
new file mode 100644
index 00000000..8532bb18
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/af.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Naudefj"
+		]
+	},
+	"ldapauthentication-desc": "Uitbreiding vir LDAP-outentisiteit wat die meeste LDAP-outentisiteitsmetodes ondersteun"
+}
diff --git a/extensions/LdapAuthentication/i18n/aln.json b/extensions/LdapAuthentication/i18n/aln.json
new file mode 100644
index 00000000..0bb91314
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/aln.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Mdupont"
+		]
+	},
+	"ldapauthentication-desc": "plugin LDAP vertetimi me mbështetje për metoda të shumta tek LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/ar.json b/extensions/LdapAuthentication/i18n/ar.json
new file mode 100644
index 00000000..0bcae038
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ar.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Meno25"
+		]
+	},
+	"ldapauthentication-desc": "إضافة تحقيق LDAP بدعم لوسائل تحقيق LDAP متعددة"
+}
diff --git a/extensions/LdapAuthentication/i18n/ast.json b/extensions/LdapAuthentication/i18n/ast.json
new file mode 100644
index 00000000..6372a65a
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ast.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Xuacu"
+		]
+	},
+	"ldapauthentication-desc": "Complemento p'autenticación LDAP con sofitu pa dellos métodos d'autenticación LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/be-tarask.json b/extensions/LdapAuthentication/i18n/be-tarask.json
new file mode 100644
index 00000000..b9177bad
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/be-tarask.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"EugeneZelenko"
+		]
+	},
+	"ldapauthentication-desc": "Дапаўненьне LDAP-аўтэнтыфікацыі з падтрымкай некалькіх мэтадаў аўтэнтыфікацыі LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/br.json b/extensions/LdapAuthentication/i18n/br.json
new file mode 100644
index 00000000..a5a0c81a
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/br.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Fulup"
+		]
+	},
+	"ldapauthentication-desc": "Adveziant gwiriekaat LDAP ennañ meur a hentenn wiriekaat LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/bs.json b/extensions/LdapAuthentication/i18n/bs.json
new file mode 100644
index 00000000..d68058da
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/bs.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"CERminator"
+		]
+	},
+	"ldapauthentication-desc": "Proširenje LDAP autentifikacije sa podrškom za mnoge metode LDAP autentifikacije"
+}
diff --git a/extensions/LdapAuthentication/i18n/ca.json b/extensions/LdapAuthentication/i18n/ca.json
new file mode 100644
index 00000000..4bbdbfb9
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ca.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Paucabot"
+		]
+	},
+	"ldapauthentication-desc": "Connector d'autentificació LDAP amb suport per a diversos mètodes d'autenticació LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/ce.json b/extensions/LdapAuthentication/i18n/ce.json
new file mode 100644
index 00000000..661857b3
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ce.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Умар"
+		]
+	},
+	"ldapauthentication-desc": "LDAP-аутентификацин плагин LDAP нийса юй хьожу"
+}
diff --git a/extensions/LdapAuthentication/i18n/cs.json b/extensions/LdapAuthentication/i18n/cs.json
new file mode 100644
index 00000000..25a46909
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/cs.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Mormegil"
+		]
+	},
+	"ldapauthentication-desc": "Autentizační modul pro LDAP podporující několik autentizačních metod LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/de.json b/extensions/LdapAuthentication/i18n/de.json
new file mode 100644
index 00000000..855a58ba
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/de.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"Imre",
+			"Kghbln"
+		]
+	},
+	"ldapauthentication-desc": "Ermöglicht die LDAP-Authentifizierung mit Hilfe mehrerer Authentifizierungsmethoden"
+}
diff --git a/extensions/LdapAuthentication/i18n/dsb.json b/extensions/LdapAuthentication/i18n/dsb.json
new file mode 100644
index 00000000..3deb89b9
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/dsb.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Michawiki"
+		]
+	},
+	"ldapauthentication-desc": "Tykac awtentifikacije LDAP z pódpěru za někotare metody LDAP-awtentifikacije"
+}
diff --git a/extensions/LdapAuthentication/i18n/en-gb.json b/extensions/LdapAuthentication/i18n/en-gb.json
new file mode 100644
index 00000000..2eb88bdb
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/en-gb.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Andibing"
+		]
+	},
+	"ldapauthentication-desc": "LDAP authentication plugin with support for multiple LDAP authentication methods"
+}
diff --git a/extensions/LdapAuthentication/i18n/en.json b/extensions/LdapAuthentication/i18n/en.json
new file mode 100644
index 00000000..1976587d
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/en.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Ryan Lane"
+		]
+	},
+	"ldapauthentication-desc": "LDAP authentication plugin with support for multiple LDAP authentication methods"
+}
\ No newline at end of file
diff --git a/extensions/LdapAuthentication/i18n/eo.json b/extensions/LdapAuthentication/i18n/eo.json
new file mode 100644
index 00000000..dbecd0c2
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/eo.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Blahma"
+		]
+	},
+	"ldapauthentication-desc": "LDAP-aÅ­tentiga kromprogramo kun subteno de pluraj LDAP-aÅ­tentigaj metodoj"
+}
diff --git a/extensions/LdapAuthentication/i18n/es.json b/extensions/LdapAuthentication/i18n/es.json
new file mode 100644
index 00000000..bb0a5130
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/es.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"Translationista",
+			"Macofe"
+		]
+	},
+	"ldapauthentication-desc": "Complemento de autenticación LDAP que admite múltiples métodos de autenticación LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/fa.json b/extensions/LdapAuthentication/i18n/fa.json
new file mode 100644
index 00000000..dc63bbd4
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/fa.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Armin1392"
+		]
+	},
+	"ldapauthentication-desc": "اعتبار افزونهٔ‌ ال‌دی‌ای‌پی با پشتیبانی برای چند زوش تأیید ال‌دی‌ای‌پی"
+}
diff --git a/extensions/LdapAuthentication/i18n/fi.json b/extensions/LdapAuthentication/i18n/fi.json
new file mode 100644
index 00000000..b2ecaa30
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/fi.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Centerlink"
+		]
+	},
+	"ldapauthentication-desc": "LDAP-todentamisliitännäinen useiden LDAP-todennustapojen tuella"
+}
diff --git a/extensions/LdapAuthentication/i18n/fr.json b/extensions/LdapAuthentication/i18n/fr.json
new file mode 100644
index 00000000..daee6e9c
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/fr.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"IAlex",
+			"Urhixidur"
+		]
+	},
+	"ldapauthentication-desc": "Extension d’authentification LDAP prenant en charge de multiples méthodes d’authentification LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/gl.json b/extensions/LdapAuthentication/i18n/gl.json
new file mode 100644
index 00000000..2dfa799c
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/gl.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Toliño"
+		]
+	},
+	"ldapauthentication-desc": "Complemento de autenticación LDAP con soporte para varios métodos de autenticación LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/gsw.json b/extensions/LdapAuthentication/i18n/gsw.json
new file mode 100644
index 00000000..081d6fe1
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/gsw.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Als-Holder"
+		]
+	},
+	"ldapauthentication-desc": "LDAP-Authentifizierigs-Plugin mit Unterstitzig fir multipli LDAP-Authentifizierigs-Merthode"
+}
diff --git a/extensions/LdapAuthentication/i18n/he.json b/extensions/LdapAuthentication/i18n/he.json
new file mode 100644
index 00000000..bd97448e
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/he.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"YaronSh"
+		]
+	},
+	"ldapauthentication-desc": "תוסף אימות LDAP עם תמיכה במספר שיטות LDAP לאימות"
+}
diff --git a/extensions/LdapAuthentication/i18n/hsb.json b/extensions/LdapAuthentication/i18n/hsb.json
new file mode 100644
index 00000000..894866d4
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/hsb.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Michawiki"
+		]
+	},
+	"ldapauthentication-desc": "Tykač awtentifikacije LDAP z podpěru za wjacore metody LDAP-awtentifikacije"
+}
diff --git a/extensions/LdapAuthentication/i18n/hu.json b/extensions/LdapAuthentication/i18n/hu.json
new file mode 100644
index 00000000..4ee3cb17
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/hu.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Glanthor Reviol"
+		]
+	},
+	"ldapauthentication-desc": "LDAP hitelesítési bővítmény többféle LDAP azonosítási módszer támogatásával"
+}
diff --git a/extensions/LdapAuthentication/i18n/ia.json b/extensions/LdapAuthentication/i18n/ia.json
new file mode 100644
index 00000000..d967d11c
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ia.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"McDutchie"
+		]
+	},
+	"ldapauthentication-desc": "Plugin pro authentication LDAP con supporto pro multiple methodos de authentication LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/id.json b/extensions/LdapAuthentication/i18n/id.json
new file mode 100644
index 00000000..95488c00
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/id.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"IvanLanin"
+		]
+	},
+	"ldapauthentication-desc": "Pengaya otentikasi LDAP dengan dukungan untuk berbagai metode otentikasi LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/it.json b/extensions/LdapAuthentication/i18n/it.json
new file mode 100644
index 00000000..7845b739
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/it.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"HalphaZ"
+		]
+	},
+	"ldapauthentication-desc": "Plugin di autenticazione LDAP con supporto a diversi metodi di autenticazione LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/ja.json b/extensions/LdapAuthentication/i18n/ja.json
new file mode 100644
index 00000000..f64dbcbf
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ja.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"Aotake",
+			"Shirayuki"
+		]
+	},
+	"ldapauthentication-desc": "複数の LDAP 認証方式対応の LDAP 認証プラグイン"
+}
diff --git a/extensions/LdapAuthentication/i18n/ko.json b/extensions/LdapAuthentication/i18n/ko.json
new file mode 100644
index 00000000..d468f649
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ko.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"아라",
+			"Revi"
+		]
+	},
+	"ldapauthentication-desc": "다양한 LDAP 인증 방식을 지원하는 LDAP 인증 플러그인"
+}
diff --git a/extensions/LdapAuthentication/i18n/ksh.json b/extensions/LdapAuthentication/i18n/ksh.json
new file mode 100644
index 00000000..4ba0ed16
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ksh.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Purodha"
+		]
+	},
+	"ldapauthentication-desc": "Dat Zohsatzprojramm för et Enlogge övver <i lang=\"en\">LDAP</i> löht ungerscheidlijje Mettoohde zoh, för et Prööfe, wä wä es."
+}
diff --git a/extensions/LdapAuthentication/i18n/lb.json b/extensions/LdapAuthentication/i18n/lb.json
new file mode 100644
index 00000000..178143a2
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/lb.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Robby"
+		]
+	},
+	"ldapauthentication-desc": "Authentifikatiouns-Plugin fir LDAP mat Ënnerstëtzung fir multipel LDAP Authentifikatiouns-Methoden"
+}
diff --git a/extensions/LdapAuthentication/i18n/lv.json b/extensions/LdapAuthentication/i18n/lv.json
new file mode 100644
index 00000000..6ca22b33
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/lv.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Papuass"
+		]
+	},
+	"ldapauthentication-desc": "LDAP autentifikācijas spraudnis ar atbalstu vairākām LDAP autentifikācijas metodēm"
+}
diff --git a/extensions/LdapAuthentication/i18n/mk.json b/extensions/LdapAuthentication/i18n/mk.json
new file mode 100644
index 00000000..b9254633
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/mk.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Bjankuloski06"
+		]
+	},
+	"ldapauthentication-desc": "LDAP приклучок за потврдување со поддршка за повеќе методи на LDAP потврдување"
+}
diff --git a/extensions/LdapAuthentication/i18n/ms.json b/extensions/LdapAuthentication/i18n/ms.json
new file mode 100644
index 00000000..573635b1
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ms.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Anakmalaysia"
+		]
+	},
+	"ldapauthentication-desc": "Pemalam pengesahan LDAP dengan sokongan untuk berbilang kaedah pengesahan LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/nb.json b/extensions/LdapAuthentication/i18n/nb.json
new file mode 100644
index 00000000..e7777640
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/nb.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Nghtwlkr"
+		]
+	},
+	"ldapauthentication-desc": "Programutvidelse for LDAP-autentisering med støtte for flere LDAP-autentiseringsmetoder"
+}
diff --git a/extensions/LdapAuthentication/i18n/nl.json b/extensions/LdapAuthentication/i18n/nl.json
new file mode 100644
index 00000000..4af7e776
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/nl.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Siebrand"
+		]
+	},
+	"ldapauthentication-desc": "LDAP-authenticatieplug-in met ondersteuning voor meerdere LDAP-authenticatiemethoden"
+}
diff --git a/extensions/LdapAuthentication/i18n/oc.json b/extensions/LdapAuthentication/i18n/oc.json
new file mode 100644
index 00000000..7ea13135
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/oc.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Cedric31"
+		]
+	},
+	"ldapauthentication-desc": "Plugin d'autentificacion LDAP amb supòrt de metòdes d'autentificacion LDAP multiples"
+}
diff --git a/extensions/LdapAuthentication/i18n/pl.json b/extensions/LdapAuthentication/i18n/pl.json
new file mode 100644
index 00000000..942ccc08
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/pl.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Sp5uhe"
+		]
+	},
+	"ldapauthentication-desc": "Wtyczka autoryzacji użytkowników z użyciem LDAP ze wsparciem dla wielu metod autoryzacji"
+}
diff --git a/extensions/LdapAuthentication/i18n/pms.json b/extensions/LdapAuthentication/i18n/pms.json
new file mode 100644
index 00000000..27814c6a
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/pms.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"Borichèt",
+			"Dragonòt"
+		]
+	},
+	"ldapauthentication-desc": "Plugin për l'autenticassion LDAP con apògg për vàire manere d'autenticassion LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/pt-br.json b/extensions/LdapAuthentication/i18n/pt-br.json
new file mode 100644
index 00000000..2d0e9f73
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/pt-br.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Giro720"
+		]
+	},
+	"ldapauthentication-desc": "''Plugin'' de autenticação LDAP, com suporte para vários métodos de autenticação"
+}
diff --git a/extensions/LdapAuthentication/i18n/pt.json b/extensions/LdapAuthentication/i18n/pt.json
new file mode 100644
index 00000000..94d5026f
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/pt.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Hamilton Abreu"
+		]
+	},
+	"ldapauthentication-desc": "''Plugin'' de autenticação LDAP, com suporte para vários métodos de autenticação"
+}
diff --git a/extensions/LdapAuthentication/i18n/qqq.json b/extensions/LdapAuthentication/i18n/qqq.json
new file mode 100644
index 00000000..f50b00c7
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/qqq.json
@@ -0,0 +1,10 @@
+{
+	"@metadata": {
+		"authors": [
+			"Fryed-peach",
+			"Shirayuki",
+			"Umherirrender"
+		]
+	},
+	"ldapauthentication-desc": "{{desc|name=LDAP Authentication|url=https://www.mediawiki.org/wiki/Extension:LDAP_Authentication}}"
+}
diff --git a/extensions/LdapAuthentication/i18n/roa-tara.json b/extensions/LdapAuthentication/i18n/roa-tara.json
new file mode 100644
index 00000000..83d6e477
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/roa-tara.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Joetaras"
+		]
+	},
+	"ldapauthentication-desc": "plugin de autendicazione LDAP cu 'u supporte pe autendicaziune multeple de metode LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/ru.json b/extensions/LdapAuthentication/i18n/ru.json
new file mode 100644
index 00000000..f19e13a4
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/ru.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Александр Сигачёв"
+		]
+	},
+	"ldapauthentication-desc": "Плагин LDAP-аутентификации с поддержкой нескольких методов проверки подлинности LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/sco.json b/extensions/LdapAuthentication/i18n/sco.json
new file mode 100644
index 00000000..25e1200d
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/sco.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"John Reid"
+		]
+	},
+	"ldapauthentication-desc": "LDAP authentication plug-in wi support fer multiple LDAP authentication methods"
+}
diff --git a/extensions/LdapAuthentication/i18n/sk.json b/extensions/LdapAuthentication/i18n/sk.json
new file mode 100644
index 00000000..5d28b363
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/sk.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Helix84"
+		]
+	},
+	"ldapauthentication-desc": "Zásuvný modul na autentifikáciu prostredníctvom LDAP s podporou viacerých metód LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/sr-ec.json b/extensions/LdapAuthentication/i18n/sr-ec.json
new file mode 100644
index 00000000..3b94c3aa
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/sr-ec.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Михајло Анђелковић"
+		]
+	},
+	"ldapauthentication-desc": "Плагин за LDAP ауторизацију, са подршком за више метода LDAP ауторизације"
+}
diff --git a/extensions/LdapAuthentication/i18n/sr-el.json b/extensions/LdapAuthentication/i18n/sr-el.json
new file mode 100644
index 00000000..b601312f
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/sr-el.json
@@ -0,0 +1,4 @@
+{
+	"@metadata": [],
+	"ldapauthentication-desc": "Plagin za LDAP autorizaciju, sa podrškom za više metoda LDAP autorizacije"
+}
diff --git a/extensions/LdapAuthentication/i18n/sv.json b/extensions/LdapAuthentication/i18n/sv.json
new file mode 100644
index 00000000..85f9e4de
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/sv.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Boivie"
+		]
+	},
+	"ldapauthentication-desc": "LDAP-autentiseringsplugin med stöd för flera LDAP-autentiseringsmetoder"
+}
diff --git a/extensions/LdapAuthentication/i18n/tl.json b/extensions/LdapAuthentication/i18n/tl.json
new file mode 100644
index 00000000..6a4df3c6
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/tl.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"AnakngAraw"
+		]
+	},
+	"ldapauthentication-desc": "Pampasak na pangpagpapatotoo ng LDAP na may suporta para sa maramihang mga pamamaraan ng pagpapatotoo ng LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/tr.json b/extensions/LdapAuthentication/i18n/tr.json
new file mode 100644
index 00000000..3c27bed5
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/tr.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Vito Genovese"
+		]
+	},
+	"ldapauthentication-desc": "Birden çok LDAP kimlik doğrulama yöntemini destekleyen LDAP kimlik doğrulama eklentisi"
+}
diff --git a/extensions/LdapAuthentication/i18n/uk.json b/extensions/LdapAuthentication/i18n/uk.json
new file mode 100644
index 00000000..92e0ec71
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/uk.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Ytsukeng Fyvaprol"
+		]
+	},
+	"ldapauthentication-desc": "Плагін LDAP-аутентифікації з підтримкою декількох методів перевірки автентичності LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/vi.json b/extensions/LdapAuthentication/i18n/vi.json
new file mode 100644
index 00000000..b0009594
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/vi.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Minh Nguyen"
+		]
+	},
+	"ldapauthentication-desc": "Phần bổ trợ xác thực LDAP hỗ trợ nhiều phương pháp xác thực LDAP"
+}
diff --git a/extensions/LdapAuthentication/i18n/yue.json b/extensions/LdapAuthentication/i18n/yue.json
new file mode 100644
index 00000000..e80107c0
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/yue.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Ktchankt"
+		]
+	},
+	"ldapauthentication-desc": "支援各種 LDAP 認證方法嘅 LDAP 認證插件"
+}
diff --git a/extensions/LdapAuthentication/i18n/zh-hans.json b/extensions/LdapAuthentication/i18n/zh-hans.json
new file mode 100644
index 00000000..5d0fa04f
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/zh-hans.json
@@ -0,0 +1,8 @@
+{
+	"@metadata": {
+		"authors": [
+			"Yanmiao liu"
+		]
+	},
+	"ldapauthentication-desc": "具有多种LDAP认证方法支持的LDAP认证插件"
+}
diff --git a/extensions/LdapAuthentication/i18n/zh-hant.json b/extensions/LdapAuthentication/i18n/zh-hant.json
new file mode 100644
index 00000000..dad712e1
--- /dev/null
+++ b/extensions/LdapAuthentication/i18n/zh-hant.json
@@ -0,0 +1,9 @@
+{
+	"@metadata": {
+		"authors": [
+			"Anakmalaysia",
+			"Cwlin0416"
+		]
+	},
+	"ldapauthentication-desc": "支援多種 LDAP 認證方法的 LDAP 認證擴充套件"
+}
diff --git a/extensions/LdapAuthentication/package.json b/extensions/LdapAuthentication/package.json
new file mode 100644
index 00000000..797ef1d5
--- /dev/null
+++ b/extensions/LdapAuthentication/package.json
@@ -0,0 +1,12 @@
+{
+  "private": true,
+  "scripts": {
+    "test": "grunt test"
+  },
+  "devDependencies": {
+    "grunt": "1.0.2",
+    "grunt-banana-checker": "0.6.0",
+    "grunt-contrib-jshint": "1.1.0",
+    "grunt-jsonlint": "1.1.0"
+  }
+}
diff --git a/extensions/LdapAuthentication/schema/ldap-mysql.sql b/extensions/LdapAuthentication/schema/ldap-mysql.sql
new file mode 100644
index 00000000..5f381e44
--- /dev/null
+++ b/extensions/LdapAuthentication/schema/ldap-mysql.sql
@@ -0,0 +1,13 @@
+CREATE TABLE /*_*/ldap_domains (
+	-- IF for domain
+	domain_id int not null primary key auto_increment,
+
+	-- Domain itself
+	domain varchar(255) binary not null,
+
+	-- User to which this domain belongs
+	user_id int not null
+
+) /*$wgDBTableOptions*/;
+
+CREATE INDEX /*i*/user_id on /*_*/ldap_domains (user_id);
diff --git a/extensions/LdapAuthentication/schema/ldap-postgres.sql b/extensions/LdapAuthentication/schema/ldap-postgres.sql
new file mode 100644
index 00000000..ff2bf6f1
--- /dev/null
+++ b/extensions/LdapAuthentication/schema/ldap-postgres.sql
@@ -0,0 +1,13 @@
+CREATE TABLE ldap_domains (
+	-- IF for domain
+	domain_id serial PRIMARY KEY,
+
+	-- Domain itself
+	domain varchar(255) not null,
+
+	-- User to which this domain belongs
+	user_id integer not null
+
+) /*$wgDBTableOptions*/;
+
+CREATE INDEX user_id on ldap_domains (user_id);
diff --git a/extensions/LdapAuthentication/version b/extensions/LdapAuthentication/version
new file mode 100644
index 00000000..3ee28ac6
--- /dev/null
+++ b/extensions/LdapAuthentication/version
@@ -0,0 +1,4 @@
+LdapAuthentication: REL1_31
+2018-04-17T22:18:54
+
+b19888c
-- 
GitLab