Compare commits
292 Commits
Author | SHA1 | Date | |
---|---|---|---|
356044fb2d | |||
368cb6bbbf | |||
14a4fe7c46 | |||
2749dfee32 | |||
033bb50298 | |||
7e1439dd3c | |||
26b16ccbe6 | |||
3c6851e38e | |||
bc0665b022 | |||
367062b76c | |||
81ad5e653e | |||
a454dac629 | |||
589364201c | |||
b36b4080f5 | |||
ac1ad47aba | |||
bfae187a59 | |||
88cb51f1bb | |||
f27812997f | |||
42460d2165 | |||
59136bd8eb | |||
53e158b553 | |||
474047ab34 | |||
922ea55cdb | |||
c97e058786 | |||
26a662c399 | |||
289aaeaa9f | |||
df79def142 | |||
de12184bf4 | |||
61acc9710b | |||
3ca062d995 | |||
04702f6090 | |||
22fb97d0c4 | |||
99f2e07f63 | |||
29fb7feb85 | |||
7d30251cd6 | |||
7173a50c36 | |||
e66280e07a | |||
3ed75822a1 | |||
7531dc362d | |||
b250908663 | |||
5f7d45e812 | |||
0fd1aa2b54 | |||
892102528b | |||
a89c663ca9 | |||
69c634ea99 | |||
a514e66969 | |||
5b98d3e00a | |||
d853082cdb | |||
c7aad627ac | |||
6ceeeaa087 | |||
4600c87787 | |||
f1c36fdeb1 | |||
d36b340692 | |||
67388884f0 | |||
223a431e8b | |||
d7ca7125ce | |||
1729b842ba | |||
4d2b78bdba | |||
106e697fc3 | |||
e0802f582b | |||
93098309cb | |||
6d4144c78d | |||
51de8283b8 | |||
016c71d30d | |||
2698fc794e | |||
ba1369d842 | |||
2836a05f90 | |||
27502ed710 | |||
3d3e975519 | |||
a559901ac0 | |||
74971a4592 | |||
16be9438b9 | |||
bb5639c447 | |||
3f32258ba0 | |||
129efd13c7 | |||
c179ed7ebb | |||
f1a85f47fd | |||
61d660be69 | |||
20a6c6f143 | |||
5b7ab65946 | |||
fb25c4395a | |||
13b60de915 | |||
0e094809fa | |||
ca179b89ea | |||
32cd18933d | |||
4f1b81ff4b | |||
cb3c8aaf2d | |||
f1f682780c | |||
b55eaea821 | |||
1d81bfb83d | |||
ec44a6740f | |||
e714286e5a | |||
bcc41b887d | |||
eee5af3081 | |||
47539de2d7 | |||
12aea4a2e2 | |||
4c135d6e59 | |||
34f49bfd01 | |||
d4621de80f | |||
39ccaa2f2d | |||
f43f986e25 | |||
c36c365a1b | |||
8dd7ee6005 | |||
80d0a017ed | |||
a17f51b72d | |||
1271317eb9 | |||
2caec48e4c | |||
0c7b4a31f1 | |||
1a01f67662 | |||
3a9344c535 | |||
c603644c26 | |||
a82e4ba363 | |||
29bc479355 | |||
e1970a8636 | |||
e321b84434 | |||
bc7d7cd313 | |||
92162bd1dc | |||
a41b6a689e | |||
2c0819d8b7 | |||
c6e0e1913f | |||
5374fa0611 | |||
0c70bb25ed | |||
4cbac22bea | |||
b23e4bce30 | |||
3763d0d485 | |||
deca0d330d | |||
963fbfbf00 | |||
10575f6f59 | |||
2f9eccf931 | |||
769d24b4b7 | |||
66aa3d6fdc | |||
ee0c0f65e3 | |||
66fa86e04e | |||
dafc3b76ea | |||
8e65d4c98d | |||
3d309ac68b | |||
9dd9f9297c | |||
41c8b6c16b | |||
be34857d71 | |||
58a991cbd0 | |||
023480bf88 | |||
4bab466169 | |||
d4070b36b9 | |||
2d98c68efd | |||
2461fec102 | |||
fa6924eb08 | |||
dab01a21c4 | |||
a0d2293a3d | |||
606b286b1e | |||
a9eb59c936 | |||
64c3d47c32 | |||
6a7ea5eeb7 | |||
ca6e1f2c5a | |||
038a712b88 | |||
64add57446 | |||
d10c6214a6 | |||
2a0d5bc92b | |||
fea9e372c8 | |||
d193f3df4a | |||
814c0dbc0f | |||
386615976e | |||
35e531a56b | |||
644e5c2e37 | |||
25f2f7df65 | |||
0691dd51f1 | |||
8441008219 | |||
e155ebe165 | |||
c7c7e4e4ea | |||
ee47026c0e | |||
eefa7ab00f | |||
121a49e9e0 | |||
8d0a0866b1 | |||
a5b18b8ab9 | |||
8c13c19b9b | |||
ce8e0fb4e3 | |||
f4ab62c4ff | |||
87c01fe2ce | |||
abb306a36e | |||
2e3cfb9546 | |||
ec2fac4ee4 | |||
5ed3420173 | |||
cb6a1c729c | |||
d3bc37b40f | |||
d54ebed189 | |||
55f3141a07 | |||
4ac39bd0d3 | |||
7c44b18854 | |||
29f4f9e9d3 | |||
627b7f9278 | |||
e5294bbecd | |||
b505a74502 | |||
1a6ea182e2 | |||
c92d84c739 | |||
fec5e13415 | |||
6bf1606997 | |||
81658a47c5 | |||
37789df696 | |||
b7c15dc15b | |||
c6831ff032 | |||
00347cb33d | |||
5277c5e0fb | |||
bf5cbd6d0e | |||
87a75b7520 | |||
7cace35cec | |||
81339b8cd0 | |||
5dae7bc168 | |||
a04207da62 | |||
0bc3d2776c | |||
c0a93fb666 | |||
e3004354c6 | |||
362d906ee7 | |||
42dc13a45d | |||
0827d8153a | |||
b9a883d3e8 | |||
2e63b5dbc7 | |||
496b213a88 | |||
bf76d3733c | |||
764d526305 | |||
43385f73ee | |||
d38b545c81 | |||
c8274e4c58 | |||
d8a1d6df5b | |||
f9a1fbd695 | |||
646dbeb5f3 | |||
8d2ac32419 | |||
e1f7aef629 | |||
8d3f0ede0d | |||
d229aee8d0 | |||
ed870dd068 | |||
2cee224450 | |||
be892b007d | |||
5588ee494d | |||
14f401f355 | |||
2770e96a8a | |||
3d01bf8feb | |||
0405f695f3 | |||
bee707be8a | |||
b95397f420 | |||
400dffa96e | |||
991f36b57f | |||
9700f8df75 | |||
36a5a194c5 | |||
d54d066870 | |||
f06a103770 | |||
f97c6e7fb0 | |||
e2fb45c9a0 | |||
5f139b69de | |||
4ad42bfe48 | |||
2db9187968 | |||
865d47121c | |||
e9e23d8808 | |||
e53714ed7c | |||
766db4f3c4 | |||
368a9c7e31 | |||
6399c0a451 | |||
ff33f9e6b3 | |||
0c85643847 | |||
d918af5d65 | |||
89c6c720f1 | |||
542bb80d85 | |||
2284117cfc | |||
b9f385d6f0 | |||
3ab33d19f6 | |||
bdcb7e263d | |||
47f51420fa | |||
0b811feccb | |||
e8c9cd56e2 | |||
d6df9d582c | |||
455a199d78 | |||
ab53d719da | |||
2f31066a0c | |||
ea71b78169 | |||
427390cbc4 | |||
f14393a8a4 | |||
bae6d1ac17 | |||
de4dcc37bc | |||
eaeb8806a1 | |||
8b9407c274 | |||
501f127f04 | |||
dcd495f4e4 | |||
c6941c7bd3 | |||
17c587d3b2 | |||
ef6bddddeb | |||
591b5e6ff1 | |||
8afe41070b | |||
292ce29b31 | |||
71347c33f1 | |||
35df787547 | |||
ba5ba051e9 | |||
e28d3a93ac | |||
3110011596 | |||
16cbf2a5f1 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@
|
||||
/database.mwb.bak
|
||||
/nbproject/private
|
||||
*.sync-conflict*
|
||||
test*
|
||||
test*
|
||||
/conf/
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "static/css/material-color"]
|
||||
path = static/css/material-color
|
||||
url = https://source.netsyms.com/Netsyms/Material-Color
|
388
LICENSE.md
388
LICENSE.md
@ -1,36 +1,362 @@
|
||||
Copyright (C) 2017 Netsyms Technologies. All rights reserved.
|
||||
Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
|
||||
|
||||
**Definitions:**
|
||||
* "the code", "the software", or "code" refers to the application (or any portion
|
||||
thereof, in any form) this license is included with, excluding third-party libraries.
|
||||
* "any form" means source code, binaries, or any other representation of the code.
|
||||
* "explicit written permission" refers to a written, signed, non-transferable
|
||||
statement from Netsyms Technologies granting you additional rights to the code.
|
||||
Licensed under the Mozilla Public License Version 2.0. Files without MPL header
|
||||
comments, including third party code, may be under a different license.
|
||||
|
||||
**You are allowed to:**
|
||||
* use the code for personal and non-commercial purposes.
|
||||
* modify the code for personal and non-commercial purposes.
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
**You are NOT allowed to:**
|
||||
* redistribute the code in any form.
|
||||
* sell the code in any form.
|
||||
* use the code for any projects you redistribute or sell, unless you have
|
||||
explicit written permission.
|
||||
* use the code for any projects that are not both personal and non-commercial.
|
||||
* use the code for commercial, business, or non-profit purposes without
|
||||
explicit written permission.
|
||||
* do anything else not permitted in this license.
|
||||
### 1. Definitions
|
||||
|
||||
**1.1. “Contributor”**
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
**1.2. “Contributor Version”**
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
**1.3. “Contribution”**
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
**1.4. “Covered Software”**
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
**1.5. “Incompatible With Secondary Licenses”**
|
||||
means
|
||||
|
||||
* **(a)** that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
* **(b)** that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
**1.6. “Executable Form”**
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
**1.7. “Larger Work”**
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
**1.8. “License”**
|
||||
means this document.
|
||||
|
||||
**1.9. “Licensable”**
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
**1.10. “Modifications”**
|
||||
means any of the following:
|
||||
|
||||
* **(a)** any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
* **(b)** any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
**1.11. “Patent Claims” of a Contributor**
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
**1.12. “Secondary License”**
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
**1.13. “Source Code Form”**
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
**1.14. “You” (or “Your”)**
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, “control” means **(a)** the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or **(b)** ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
|
||||
### 2. License Grants and Conditions
|
||||
|
||||
#### 2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
* **(a)** under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
* **(b)** under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
#### 2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
#### 2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
* **(a)** for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
* **(b)** for infringements caused by: **(i)** Your and any other third party's
|
||||
modifications of Covered Software, or **(ii)** the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
* **(c)** under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
#### 2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
#### 2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
#### 2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
#### 2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
|
||||
### 3. Responsibilities
|
||||
|
||||
#### 3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
#### 3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
* **(a)** such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
* **(b)** You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
#### 3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
#### 3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
#### 3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
|
||||
### 4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: **(a)** comply with
|
||||
the terms of this License to the maximum extent possible; and **(b)**
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
|
||||
### 5. Termination
|
||||
|
||||
**5.1.** The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated **(a)** provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and **(b)** on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
**5.2.** If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
|
||||
### 6. Disclaimer of Warranty
|
||||
|
||||
> Covered Software is provided under this License on an “as is”
|
||||
> basis, without warranty of any kind, either expressed, implied, or
|
||||
> statutory, including, without limitation, warranties that the
|
||||
> Covered Software is free of defects, merchantable, fit for a
|
||||
> particular purpose or non-infringing. The entire risk as to the
|
||||
> quality and performance of the Covered Software is with You.
|
||||
> Should any Covered Software prove defective in any respect, You
|
||||
> (not any Contributor) assume the cost of any necessary servicing,
|
||||
> repair, or correction. This disclaimer of warranty constitutes an
|
||||
> essential part of this License. No use of any Covered Software is
|
||||
> authorized under this License except under this disclaimer.
|
||||
|
||||
### 7. Limitation of Liability
|
||||
|
||||
> Under no circumstances and under no legal theory, whether tort
|
||||
> (including negligence), contract, or otherwise, shall any
|
||||
> Contributor, or anyone who distributes Covered Software as
|
||||
> permitted above, be liable to You for any direct, indirect,
|
||||
> special, incidental, or consequential damages of any character
|
||||
> including, without limitation, damages for lost profits, loss of
|
||||
> goodwill, work stoppage, computer failure or malfunction, or any
|
||||
> and all other commercial damages or losses, even if such party
|
||||
> shall have been informed of the possibility of such damages. This
|
||||
> limitation of liability shall not apply to liability for death or
|
||||
> personal injury resulting from such party's negligence to the
|
||||
> extent applicable law prohibits such limitation. Some
|
||||
> jurisdictions do not allow the exclusion or limitation of
|
||||
> incidental or consequential damages, so this exclusion and
|
||||
> limitation may not apply to You.
|
||||
|
||||
|
||||
### 8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
|
||||
### 9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
### 10. Versions of the License
|
||||
|
||||
#### 10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
#### 10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
#### 10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
## Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
## Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
**You must:**
|
||||
* retain any and all copyright notices in the code.
|
||||
* retain any and all license notices in the code.
|
||||
* ask for clarification from Netsyms Technologies if any portion of this license
|
||||
is unclear or ambiguous.
|
||||
|
||||
**Disclaimer:**
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
||||
AccountHub
|
||||
======
|
||||
|
||||
AccountHub is a web application enabling secure self-serve account management.
|
||||
Employees can change their password and manage other web apps they have access
|
||||
to with the dashboard.
|
||||
|
||||
https://netsyms.biz/apps/accounthub
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
0. Setup a LAMP server with PHP 7.2, including PHP extensions mbstring, zip, gd, and imagick
|
||||
1. Copy `settings.template.php` to `settings.php`
|
||||
2. Import `database.sql` into your database server
|
||||
3. Edit `settings.php` and fill in your DB info
|
||||
4. Set the URL of the install
|
||||
5. Setup "EXTERNAL_APPS" with specifics for your install.
|
||||
6. Setup the email settings to receive alerts you configure later in ManagePanel
|
||||
7. Run `composer install` (or `composer.phar install`) to install dependency libraries
|
||||
8. Edit the database table `apikeys` and add some API keys for the other apps to use
|
||||
9. From a web browser, visit `http://apps/url` (or whatever your setup is). If you did everything right, you should see a login screen.
|
||||
10. Now go to `http://apps/url/setup.php` and create an admin account.
|
||||
11. Install [ManagePanel](https://source.netsyms.com/Business/ManagePanel) to setup additional user accounts.
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
1. Run `git pull` or otherwise update the code
|
||||
2. Run `composer install` to update dependencies
|
||||
3. Execute the SQL scripts in `database_upgrade` to take you from your current version to the latest version
|
||||
4. Rewrite your `settings.php` based on the new `settings.template.php`
|
108
action.php
108
action.php
@ -1,13 +1,16 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Make things happen when buttons are pressed and forms submitted.
|
||||
*/
|
||||
use LdapTools\LdapManager;
|
||||
use LdapTools\Object\LdapObjectType;
|
||||
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
use OTPHP\TOTP;
|
||||
|
||||
// If the user presses Sign Out but we're not logged in anymore,
|
||||
// we don't want to show a nasty error.
|
||||
if ($VARS['action'] == 'signout' && $_SESSION['loggedin'] != true) {
|
||||
@ -18,49 +21,100 @@ if ($VARS['action'] == 'signout' && $_SESSION['loggedin'] != true) {
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
|
||||
function returnToSender($msg, $arg = "") {
|
||||
global $VARS;
|
||||
if ($arg == "") {
|
||||
header("Location: home.php?page=" . urlencode($VARS['source']) . "&msg=$msg");
|
||||
} else {
|
||||
header("Location: home.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=" . urlencode($arg));
|
||||
$header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg";
|
||||
if ($arg != "") {
|
||||
$header .= "&arg=$arg";
|
||||
}
|
||||
header($header);
|
||||
die();
|
||||
}
|
||||
|
||||
switch ($VARS['action']) {
|
||||
case "signout":
|
||||
insertAuthLog(11, $_SESSION['uid']);
|
||||
Log::insert(LogType::LOGOUT, $_SESSION['uid']);
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
header('Location: index.php?logout=1');
|
||||
die("Logged out.");
|
||||
case "chpasswd":
|
||||
engageRateLimit();
|
||||
$error = [];
|
||||
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
|
||||
if ($result === TRUE) {
|
||||
returnToSender("password_updated");
|
||||
$user = new User($_SESSION['uid']);
|
||||
try {
|
||||
$result = $user->changePassword($VARS['oldpass'], $VARS['newpass'], $VARS['conpass']);
|
||||
|
||||
if ($result === TRUE) {
|
||||
returnToSender("password_updated");
|
||||
}
|
||||
} catch (PasswordMatchException $e) {
|
||||
returnToSender("passwords_same");
|
||||
} catch (PasswordMismatchException $e) {
|
||||
returnToSender("new_password_mismatch");
|
||||
} catch (IncorrectPasswordException $e) {
|
||||
returnToSender("old_password_mismatch");
|
||||
} catch (WeakPasswordException $e) {
|
||||
returnToSender("weak_password");
|
||||
}
|
||||
switch (count($error)) {
|
||||
case 1:
|
||||
returnToSender($error[0]);
|
||||
case 2:
|
||||
returnToSender($error[0], $error[1]);
|
||||
default:
|
||||
returnToSender("generic_op_error");
|
||||
break;
|
||||
case "chpin":
|
||||
engageRateLimit();
|
||||
$error = [];
|
||||
if (!($VARS['newpin'] == "" || (is_numeric($VARS['newpin']) && strlen($VARS['newpin']) >= 1 && strlen($VARS['newpin']) <= 8))) {
|
||||
returnToSender("invalid_pin_format");
|
||||
}
|
||||
if ($VARS['newpin'] == $VARS['conpin']) {
|
||||
$database->update('accounts', ['pin' => ($VARS['newpin'] == "" ? null : $VARS['newpin'])], ['uid' => $_SESSION['uid']]);
|
||||
returnToSender("pin_updated");
|
||||
}
|
||||
returnToSender("new_pin_mismatch");
|
||||
break;
|
||||
case "add2fa":
|
||||
if (is_empty($VARS['secret'])) {
|
||||
if (empty($VARS['secret'])) {
|
||||
returnToSender("invalid_parameters");
|
||||
}
|
||||
$database->update('accounts', ['authsecret' => $VARS['secret']], ['uid' => $_SESSION['uid']]);
|
||||
insertAuthLog(9, $_SESSION['uid']);
|
||||
$user = new User($_SESSION['uid']);
|
||||
$totp = new TOTP(null, $VARS['secret']);
|
||||
if (!$totp->verify($VARS["totpcode"])) {
|
||||
returnToSender("2fa_wrong_code");
|
||||
}
|
||||
$user->save2fa($VARS['secret']);
|
||||
Log::insert(LogType::ADDED_2FA, $user);
|
||||
returnToSender("2fa_enabled");
|
||||
case "rm2fa":
|
||||
$database->update('accounts', ['authsecret' => ""], ['uid' => $_SESSION['uid']]);
|
||||
insertAuthLog(10, $_SESSION['uid']);
|
||||
engageRateLimit();
|
||||
(new User($_SESSION['uid']))->save2fa("");
|
||||
Log::insert(LogType::REMOVED_2FA, $_SESSION['uid']);
|
||||
returnToSender("2fa_removed");
|
||||
break;
|
||||
}
|
||||
case "readnotification":
|
||||
$user = new User($_SESSION['uid']);
|
||||
|
||||
if (empty($VARS['id'])) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
try {
|
||||
Notifications::read($user, $VARS['id']);
|
||||
returnToSender("#notifications");
|
||||
} catch (Exception $ex) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
break;
|
||||
case "deletenotification":
|
||||
$user = new User($_SESSION['uid']);
|
||||
|
||||
if (empty($VARS['id'])) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
try {
|
||||
Notifications::delete($user, $VARS['id']);
|
||||
returnToSender("notification_deleted#notifications");
|
||||
} catch (Exception $ex) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
break;
|
||||
case "resetfeedkey":
|
||||
$database->delete('userkeys', ['AND' => ['uid' => $_SESSION['uid'], 'typeid' => 1]]);
|
||||
returnToSender("feed_key_reset");
|
||||
break;
|
||||
}
|
||||
|
264
api.php
264
api.php
@ -1,263 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Simple JSON API to allow other apps to access accounts in this system.
|
||||
*
|
||||
* Requests can be sent via either GET or POST requests. POST is recommended
|
||||
* as it has a lower chance of being logged on the server, exposing unencrypted
|
||||
* user passwords.
|
||||
*/
|
||||
require __DIR__ . '/required.php';
|
||||
require_once __DIR__ . '/lib/login.php';
|
||||
header("Content-Type: application/json");
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//try {
|
||||
$key = $VARS['key'];
|
||||
if ($database->has('apikeys', ['key' => $key]) !== TRUE) {
|
||||
header("HTTP/1.1 403 Unauthorized");
|
||||
insertAuthLog(14, null, "Key: " . $key);
|
||||
die("\"403 Unauthorized\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API key with most of the characters replaced with *s.
|
||||
* @global string $key
|
||||
* @return string
|
||||
*/
|
||||
function getCensoredKey() {
|
||||
global $key;
|
||||
$resp = $key;
|
||||
if (strlen($key) > 5) {
|
||||
for ($i = 2; $i < strlen($key) - 2; $i++) {
|
||||
$resp[$i] = "*";
|
||||
}
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
|
||||
switch ($VARS['action']) {
|
||||
case "ping":
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
break;
|
||||
case "auth":
|
||||
$errmsg = "";
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
|
||||
insertAuthLog(12, null, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK", "msg" => lang("login successful", false)]));
|
||||
} else {
|
||||
insertAuthLog(13, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
if (!is_empty($errmsg)) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang2("ldap error", ['error' => $errmsg], false)]));
|
||||
}
|
||||
if (user_exists($VARS['username'])) {
|
||||
switch (get_account_status($VARS['username'])) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
|
||||
case "TERMINATED":
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
|
||||
case "CHANGE_PASSWORD":
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
|
||||
case "NORMAL":
|
||||
break;
|
||||
default:
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "userinfo":
|
||||
if (!is_empty($VARS['username'])) {
|
||||
if (user_exists_local($VARS['username'])) {
|
||||
$data = $database->select("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["username" => $VARS['username']])[0];
|
||||
exit(json_encode(["status" => "OK", "data" => $data]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
} else if (!is_empty($VARS['uid'])) {
|
||||
if ($database->has('accounts', ['uid' => $VARS['uid']])) {
|
||||
$data = $database->select("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["uid" => $VARS['uid']])[0];
|
||||
exit(json_encode(["status" => "OK", "data" => $data]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
break;
|
||||
case "userexists":
|
||||
if (!is_empty($VARS['uid'])) {
|
||||
if ($database->has('accounts', ['uid' => $VARS['uid']])) {
|
||||
exit(json_encode(["status" => "OK", "exists" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "exists" => false]));
|
||||
}
|
||||
}
|
||||
if (user_exists_local($VARS['username'])) {
|
||||
exit(json_encode(["status" => "OK", "exists" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "exists" => false]));
|
||||
}
|
||||
break;
|
||||
case "hastotp":
|
||||
if (userHasTOTP($VARS['username'])) {
|
||||
exit(json_encode(["status" => "OK", "otp" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "otp" => false]));
|
||||
}
|
||||
break;
|
||||
case "verifytotp":
|
||||
if (verifyTOTP($VARS['username'], $VARS['code'])) {
|
||||
exit(json_encode(["status" => "OK", "valid" => true]));
|
||||
} else {
|
||||
insertAuthLog(7, null, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("2fa incorrect", false), "valid" => false]));
|
||||
}
|
||||
break;
|
||||
case "acctstatus":
|
||||
exit(json_encode(["status" => "OK", "account" => get_account_status($VARS['username'])]));
|
||||
case "login":
|
||||
// simulate a login, checking account status and alerts
|
||||
$errmsg = "";
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
|
||||
$uid = $database->select('accounts', 'uid', ['username' => $VARS['username']])[0];
|
||||
switch (get_account_status($VARS['username'])) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
|
||||
case "TERMINATED":
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
|
||||
case "CHANGE_PASSWORD":
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
|
||||
case "NORMAL":
|
||||
insertAuthLog(4, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
case "ALERT_ON_ACCESS":
|
||||
sendLoginAlertEmail($VARS['username']);
|
||||
insertAuthLog(4, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK", "alert" => true]));
|
||||
default:
|
||||
insertAuthLog(5, $uid, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
|
||||
}
|
||||
} else {
|
||||
insertAuthLog(5, null, "Username: " . $VARS['username'] . ", Key: " . getCensoredKey());
|
||||
if (!is_empty($errmsg)) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang2("ldap error", ['error' => $errmsg], false)]));
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "ismanagerof":
|
||||
if ($VARS['uid'] === 1) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['manager']])) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['employee']])) {
|
||||
$managerid = $VARS['manager'];
|
||||
$employeeid = $VARS['employee'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['employee']]));
|
||||
}
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['manager']]));
|
||||
}
|
||||
} else {
|
||||
if (user_exists_local($VARS['manager'])) {
|
||||
if (user_exists_local($VARS['employee'])) {
|
||||
$managerid = $database->select('accounts', 'uid', ['username' => $VARS['manager']]);
|
||||
$employeeid = $database->select('accounts', 'uid', ['username' => $VARS['employee']]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['employee']]));
|
||||
}
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['manager']]));
|
||||
}
|
||||
}
|
||||
if ($database->has('managers', ['AND' => ['managerid' => $managerid, 'employeeid' => $employeeid]])) {
|
||||
exit(json_encode(["status" => "OK", "managerof" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "managerof" => false]));
|
||||
}
|
||||
break;
|
||||
case "getmanaged":
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$managerid = $VARS['uid'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else if ($VARS['username']) {
|
||||
if ($database->has("accounts", ['username' => $VARS['username']])) {
|
||||
$managerid = $database->select('accounts', 'uid', ['username' => $VARS['username']]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$managed = $database->select('managers', 'employeeid', ['managerid' => $managerid]);
|
||||
exit(json_encode(["status" => "OK", "employees" => $managed]));
|
||||
break;
|
||||
case "getmanagers":
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$empid = $VARS['uid'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else if ($VARS['username']) {
|
||||
if ($database->has("accounts", ['username' => $VARS['username']])) {
|
||||
$empid = $database->select('accounts', 'uid', ['username' => $VARS['username']]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$managers = $database->select('managers', 'managerid', ['employeeid' => $empid]);
|
||||
exit(json_encode(["status" => "OK", "managers" => $managers]));
|
||||
break;
|
||||
case "usersearch":
|
||||
if (is_empty($VARS['search']) || strlen($VARS['search']) < 3) {
|
||||
exit(json_encode(["status" => "OK", "result" => []]));
|
||||
}
|
||||
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]);
|
||||
exit(json_encode(["status" => "OK", "result" => $data]));
|
||||
break;
|
||||
case "permission":
|
||||
if (is_empty($VARS['code'])) {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$perm = $VARS['code'];
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$user = $database->select('accounts', ['username'], ['uid' => $VARS['uid']])[0]['username'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else if ($VARS['username']) {
|
||||
if ($database->has("accounts", ['username' => $VARS['username']])) {
|
||||
$user = $VARS['username'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$hasperm = account_has_permission($user, $perm);
|
||||
exit(json_encode(["status" => "OK", "has_permission" => $hasperm]));
|
||||
break;
|
||||
default:
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
/* } catch (Exception $e) {
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
die("\"500 Internal Server Error\"");
|
||||
} */
|
||||
// Load in new API from legacy location (a.k.a. here)
|
||||
require __DIR__ . "/api/index.php";
|
||||
|
5
api/.htaccess
Normal file
5
api/.htaccess
Normal file
@ -0,0 +1,5 @@
|
||||
# Rewrite for Nextcloud Notes API
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
|
||||
</IfModule>
|
9
api/actions/acctstatus.php
Normal file
9
api/actions/acctstatus.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
sendJsonResp(null, "OK", ["account" => User::byUsername($VARS['username'])->getStatus()->getString()]);
|
14
api/actions/addapppassword.php
Normal file
14
api/actions/addapppassword.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($VARS['desc']);
|
||||
$chunk_code = str_replace(" ", "-", trim(chunk_split($code, 5, ' ')));
|
||||
$database->insert('apppasswords', ['uid' => User::byUsername($VARS['username'])->getUID(), 'hash' => password_hash($chunk_code, PASSWORD_DEFAULT), 'description' => $desc]);
|
||||
|
||||
sendJsonResp("", "OK", ["pass" => $chunk_code]);
|
29
api/actions/addnotification.php
Normal file
29
api/actions/addnotification.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
try {
|
||||
$timestamp = "";
|
||||
if (!empty($VARS['timestamp'])) {
|
||||
$timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
|
||||
}
|
||||
$url = "";
|
||||
if (!empty($VARS['url'])) {
|
||||
$url = $VARS['url'];
|
||||
}
|
||||
$nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
|
||||
|
||||
exitWithJson(["status" => "OK", "id" => $nid]);
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
18
api/actions/alertemail.php
Normal file
18
api/actions/alertemail.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
engageRateLimit();
|
||||
$appname = "???";
|
||||
if (!empty($VARS['appname'])) {
|
||||
$appname = $VARS['appname'];
|
||||
}
|
||||
$result = User::byUsername($VARS['username'])->sendAlertEmail($appname);
|
||||
if ($result === TRUE) {
|
||||
sendJsonResp();
|
||||
}
|
||||
sendJsonResp($result, "ERROR");
|
39
api/actions/auth.php
Normal file
39
api/actions/auth.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$user = User::byUsername($VARS['username']);
|
||||
|
||||
$ok = false;
|
||||
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
|
||||
$ok = true;
|
||||
} else {
|
||||
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
|
||||
$ok = true;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
sendJsonResp($Strings->get("login successful", false), "OK");
|
||||
} else {
|
||||
Log::insert(LogType::API_AUTH_FAILED, $user->getUID(), "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
if ($user->exists()) {
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
sendJsonResp($Strings->get("account locked", false), "ERROR");
|
||||
case AccountStatus::TERMINATED:
|
||||
sendJsonResp($Strings->get("account terminated", false), "ERROR");
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
sendJsonResp($Strings->get("password expired", false), "ERROR");
|
||||
case AccountStatus::NORMAL:
|
||||
break;
|
||||
default:
|
||||
sendJsonResp($Strings->get("account state error", false), "ERROR");
|
||||
}
|
||||
}
|
||||
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
|
||||
}
|
15
api/actions/checkloginkey.php
Normal file
15
api/actions/checkloginkey.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
try {
|
||||
$uid = LoginKey::getuid($VARS['code']);
|
||||
|
||||
exitWithJson(["status" => "OK", "uid" => $uid]);
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp("", "ERROR");
|
||||
}
|
24
api/actions/checkpin.php
Normal file
24
api/actions/checkpin.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$pin = "";
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
if ($user->exists()) {
|
||||
$pin = $database->get("accounts", "pin", ["uid" => $user->getUID()]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
|
||||
}
|
||||
if (is_null($pin) || $pin == "") {
|
||||
exitWithJson(["status" => "ERROR", "pinvalid" => false, "nopinset" => true]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "pinvalid" => ($pin == $VARS['pin'])]);
|
15
api/actions/codelogin.php
Normal file
15
api/actions/codelogin.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
|
||||
if ($database->has("onetimekeys", ["key" => $VARS['code'], "expires[>]" => date("Y-m-d H:i:s")])) {
|
||||
$user = $database->get("onetimekeys", ["[>]accounts" => ["uid" => "uid"]], ["username", "realname", "accounts.uid"], ["key" => $VARS['code']]);
|
||||
exitWithJson(["status" => "OK", "user" => $user]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("no such code or code expired", false), "ERROR");
|
||||
}
|
20
api/actions/deletenotification.php
Normal file
20
api/actions/deletenotification.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
try {
|
||||
Notifications::delete($user, $VARS['id']);
|
||||
sendJsonResp();
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
10
api/actions/getgroups.php
Normal file
10
api/actions/getgroups.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$groups = $database->select('groups', ['groupid (id)', 'groupname (name)']);
|
||||
exitWithJson(["status" => "OK", "groups" => $groups]);
|
23
api/actions/getgroupsbyuser.php
Normal file
23
api/actions/getgroupsbyuser.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$empid = $VARS['uid'];
|
||||
} else {
|
||||
sendJsonResp($Strings->get("user does not exist", false), "ERROR");
|
||||
}
|
||||
} else if (!empty($VARS['username'])) {
|
||||
if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) {
|
||||
$empid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("user does not exist", false), "ERROR");
|
||||
}
|
||||
}
|
||||
$groups = $database->select('assigned_groups', ["[>]groups" => ["groupid" => "groupid"]], ['groups.groupid (id)', 'groups.groupname (name)'], ['uid' => $empid]);
|
||||
exitWithJson(["status" => "OK", "groups" => $groups]);
|
22
api/actions/getloginkey.php
Normal file
22
api/actions/getloginkey.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$appicon = null;
|
||||
if (!empty($VARS['appicon'])) {
|
||||
$appicon = $VARS['appicon'];
|
||||
}
|
||||
|
||||
$code = LoginKey::generate($VARS['appname'], $appicon);
|
||||
|
||||
if (strpos($SETTINGS['url'], "http") === 0) {
|
||||
$url = $SETTINGS['url'] . "login/";
|
||||
} else {
|
||||
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'] . "login/";
|
||||
}
|
||||
|
||||
exitWithJson(["status" => "OK", "code" => $code, "loginurl" => $url]);
|
23
api/actions/getmanaged.php
Normal file
23
api/actions/getmanaged.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
$manager = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$manager = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
if (!$manager->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
|
||||
}
|
||||
if (!empty($VARS['get']) && $VARS['get'] == "username") {
|
||||
$managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $manager->getUID()]);
|
||||
} else {
|
||||
$managed = $database->select('managers', 'employeeid', ['managerid' => $manager->getUID()]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "employees" => $managed]);
|
19
api/actions/getmanagers.php
Normal file
19
api/actions/getmanagers.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
$emp = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$emp = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
if (!$emp->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
|
||||
}
|
||||
$managers = $database->select('managers', 'managerid', ['employeeid' => $emp->getUID()]);
|
||||
exitWithJson(["status" => "OK", "managers" => $managers]);
|
20
api/actions/getnotifications.php
Normal file
20
api/actions/getnotifications.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
|
||||
try {
|
||||
$notifications = Notifications::get($user);
|
||||
exitWithJson(["status" => "OK", "notifications" => $notifications]);
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
29
api/actions/getusersbygroup.php
Normal file
29
api/actions/getusersbygroup.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if ($database->has("groups", ['groupid' => $VARS['gid']])) {
|
||||
$groupid = $VARS['gid'];
|
||||
} else {
|
||||
sendJsonResp($Strings->get("group does not exist", false), "ERROR");
|
||||
}
|
||||
|
||||
if (!empty($VARS["get"]) && $VARS['get'] == "username") {
|
||||
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], 'username', ['groupid' => $groupid, "ORDER" => "username"]);
|
||||
} else if (!empty($VARS["get"]) && $VARS['get'] == "detail") {
|
||||
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], ['username', 'realname (name)', 'accounts.uid', 'pin'], ['groupid' => $groupid, "ORDER" => "realname"]);
|
||||
for ($i = 0; $i < count($users); $i++) {
|
||||
if (is_null($users[$i]['pin']) || $users[$i]['pin'] == "") {
|
||||
$users[$i]['pin'] = false;
|
||||
} else {
|
||||
$users[$i]['pin'] = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$users = $database->select('assigned_groups', 'uid', ['groupid' => $groupid]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "users" => $users]);
|
13
api/actions/groupsearch.php
Normal file
13
api/actions/groupsearch.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (strlen($VARS['search']) < 2) {
|
||||
exitWithJson(["status" => "OK", "result" => []]);
|
||||
}
|
||||
$data = $database->select('groups', ['groupid (id)', 'groupname (name)'], ['groupname[~]' => $VARS['search'], "LIMIT" => 10]);
|
||||
exitWithJson(["status" => "OK", "result" => $data]);
|
9
api/actions/hastotp.php
Normal file
9
api/actions/hastotp.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
sendJsonResp(null, "OK", ["otp" => User::byUsername($VARS['username'])->has2fa()]);
|
27
api/actions/ismanagerof.php
Normal file
27
api/actions/ismanagerof.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid']) && $VARS['uid'] == "1") {
|
||||
$manager = new User($VARS['manager']);
|
||||
$employee = new User($VARS['employee']);
|
||||
} else {
|
||||
$manager = User::byUsername($VARS['manager']);
|
||||
$employee = User::byUsername($VARS['employee']);
|
||||
}
|
||||
if (!$manager->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['manager']]);
|
||||
}
|
||||
if (!$employee->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['employee']]);
|
||||
}
|
||||
|
||||
if ($database->has('managers', ['AND' => ['managerid' => $manager->getUID(), 'employeeid' => $employee->getUID()]])) {
|
||||
exitWithJson(["status" => "OK", "managerof" => true]);
|
||||
} else {
|
||||
exitWithJson(["status" => "OK", "managerof" => false]);
|
||||
}
|
16
api/actions/listapps.php
Normal file
16
api/actions/listapps.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$apps = $SETTINGS['apps'];
|
||||
// Format paths as absolute URLs
|
||||
foreach ($apps as $k => $v) {
|
||||
if (strpos($apps[$k]['url'], "http") === FALSE) {
|
||||
$apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url'];
|
||||
}
|
||||
}
|
||||
exitWithJson(["status" => "OK", "apps" => $apps]);
|
46
api/actions/login.php
Normal file
46
api/actions/login.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($VARS['username']);
|
||||
|
||||
$ok = false;
|
||||
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
|
||||
$ok = true;
|
||||
} else {
|
||||
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
|
||||
$ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
switch ($user->getStatus()->getString()) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account locked", false)]);
|
||||
case "TERMINATED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)]);
|
||||
case "CHANGE_PASSWORD":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("password expired", false)]);
|
||||
case "NORMAL":
|
||||
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "OK"]);
|
||||
case "ALERT_ON_ACCESS":
|
||||
$user->sendAlertEmail();
|
||||
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "OK", "alert" => true]);
|
||||
default:
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account state error", false)]);
|
||||
}
|
||||
} else {
|
||||
Log::insert(LogType::API_LOGIN_FAILED, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]);
|
||||
}
|
9
api/actions/mobileenabled.php
Normal file
9
api/actions/mobileenabled.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
exitWithJson(["status" => "OK", "mobile" => $SETTINGS['mobile_enabled']]);
|
15
api/actions/mobilevalid.php
Normal file
15
api/actions/mobilevalid.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (empty($VARS['username']) || empty($VARS['code'])) {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$code = strtoupper($VARS['code']);
|
||||
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $code, 'accounts.username' => strtolower($VARS['username'])]]);
|
||||
exitWithJson(["status" => "OK", "valid" => $user_key_valid]);
|
19
api/actions/permission.php
Normal file
19
api/actions/permission.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$perm = $VARS['code'];
|
||||
if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
if (!$user->exists()) {
|
||||
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
|
||||
}
|
||||
exitWithJson(["status" => "OK", "has_permission" => $user->hasPermission($perm)]);
|
9
api/actions/ping.php
Normal file
9
api/actions/ping.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
sendJsonResp();
|
25
api/actions/readnotification.php
Normal file
25
api/actions/readnotification.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (empty($VARS['id'])) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
|
||||
}
|
||||
try {
|
||||
Notifications::read($user, $VARS['id']);
|
||||
sendJsonResp();
|
||||
} catch (Exception $ex) {
|
||||
sendJsonResp($ex->getMessage(), "ERROR");
|
||||
}
|
15
api/actions/userexists.php
Normal file
15
api/actions/userexists.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
}
|
||||
|
||||
sendJsonResp(null, "OK", ["exists" => $user->exists()]);
|
20
api/actions/userinfo.php
Normal file
20
api/actions/userinfo.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
}
|
||||
if ($user->exists()) {
|
||||
$data = $database->get("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"], 'pin'], ["uid" => $user->getUID()]);
|
||||
$data['pin'] = (is_null($data['pin']) || $data['pin'] == "" ? false : true);
|
||||
sendJsonResp(null, "OK", ["data" => $data]);
|
||||
} else {
|
||||
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
|
||||
}
|
13
api/actions/usersearch.php
Normal file
13
api/actions/usersearch.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
if (strlen($VARS['search']) < 3) {
|
||||
exitWithJson(["status" => "OK", "result" => []]);
|
||||
}
|
||||
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]);
|
||||
exitWithJson(["status" => "OK", "result" => $data]);
|
15
api/actions/verifytotp.php
Normal file
15
api/actions/verifytotp.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->check2fa($VARS['code'])) {
|
||||
sendJsonResp(null, "OK", ["valid" => true]);
|
||||
} else {
|
||||
Log::insert(LogType::API_BAD_2FA, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
sendJsonResp($Strings->get("2fa incorrect", false), "ERROR", ["valid" => false]);
|
||||
}
|
267
api/apisettings.php
Normal file
267
api/apisettings.php
Normal file
@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$APIS = [
|
||||
"ping" => [
|
||||
"load" => "ping.php",
|
||||
"vars" => [
|
||||
],
|
||||
"permission" => [
|
||||
],
|
||||
"keytype" => "NONE"
|
||||
],
|
||||
"auth" => [
|
||||
"load" => "auth.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"password" => "string",
|
||||
"apppass (optional)" => "/[0-1]/"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"userinfo" => [
|
||||
"load" => "userinfo.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"userexists" => [
|
||||
"load" => "userexists.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
]
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"hastotp" => [
|
||||
"load" => "hastotp.php",
|
||||
"vars" => [
|
||||
"username" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"verifytotp" => [
|
||||
"load" => "verifytotp.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"acctstatus" => [
|
||||
"load" => "acctstatus.php",
|
||||
"vars" => [
|
||||
"username" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"login" => [
|
||||
"load" => "login.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"password" => "string",
|
||||
"apppass (optional)" => "/[0-1]/"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"ismanagerof" => [
|
||||
"load" => "ismanagerof.php",
|
||||
"vars" => [
|
||||
"manager" => "string",
|
||||
"employee" => "string",
|
||||
"uid (optional)" => "numeric"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getmanaged" => [
|
||||
"load" => "getmanaged.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
],
|
||||
"get (optional)" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getmanagers" => [
|
||||
"load" => "getmanagers.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"usersearch" => [
|
||||
"load" => "usersearch.php",
|
||||
"vars" => [
|
||||
"search" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"permission" => [
|
||||
"load" => "permission.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"username" => "string",
|
||||
"uid" => "numeric"
|
||||
],
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"mobileenabled" => [
|
||||
"load" => "mobileenabled.php",
|
||||
"keytype" => "NONE"
|
||||
],
|
||||
"mobilevalid" => [
|
||||
"load" => "mobilevalid.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"alertemail" => [
|
||||
"load" => "alertemail.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"appname (optional)" => "string"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"codelogin" => [
|
||||
"load" => "codelogin.php",
|
||||
"vars" => [
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"listapps" => [
|
||||
"load" => "listapps.php",
|
||||
"keytype" => "NONE"
|
||||
],
|
||||
"getusersbygroup" => [
|
||||
"load" => "getusersbygroup.php",
|
||||
"vars" => [
|
||||
"gid" => "numeric",
|
||||
"get (optional)" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getgroupsbyuser" => [
|
||||
"load" => "getgroupsbyuser.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"getgroups" => [
|
||||
"load" => "getgroups.php",
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"groupsearch" => [
|
||||
"load" => "groupsearch.php",
|
||||
"vars" => [
|
||||
"search" => "string"
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"checkpin" => [
|
||||
"load" => "checkpin.php",
|
||||
"vars" => [
|
||||
"pin" => "string",
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
]
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"getnotifications" => [
|
||||
"load" => "getnotifications.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
]
|
||||
],
|
||||
"keytype" => "READ"
|
||||
],
|
||||
"readnotification" => [
|
||||
"load" => "readnotification.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
],
|
||||
"id" => "numeric"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"addnotification" => [
|
||||
"load" => "addnotification.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
],
|
||||
"title" => "string",
|
||||
"content" => "string",
|
||||
"timestamp (optional)" => "string",
|
||||
"url (optional)" => "string",
|
||||
"sensitive (optional)" => "string"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"deletenotification" => [
|
||||
"load" => "deletenotification.php",
|
||||
"vars" => [
|
||||
"OR" => [
|
||||
"uid" => "numeric",
|
||||
"username" => "string"
|
||||
],
|
||||
"id" => "numeric"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
"getloginkey" => [
|
||||
"load" => "getloginkey.php",
|
||||
"vars" => [
|
||||
"appname" => "string",
|
||||
"appicon (optional)" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"checkloginkey" => [
|
||||
"load" => "checkloginkey.php",
|
||||
"vars" => [
|
||||
"code" => "string"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
"addapppassword" => [
|
||||
"load" => "addapppassword.php",
|
||||
"vars" => [
|
||||
"desc" => "string",
|
||||
"username" => "string"
|
||||
],
|
||||
"keytype" => "FULL"
|
||||
],
|
||||
];
|
164
api/functions.php
Normal file
164
api/functions.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build and send a simple JSON response.
|
||||
* @param string $msg A message
|
||||
* @param string $status "OK" or "ERROR"
|
||||
* @param array $data More JSON data
|
||||
*/
|
||||
function sendJsonResp(string $msg = null, string $status = "OK", array $data = null) {
|
||||
$resp = [];
|
||||
if (!is_null($data)) {
|
||||
$resp = $data;
|
||||
}
|
||||
if (!is_null($msg)) {
|
||||
$resp["msg"] = $msg;
|
||||
}
|
||||
$resp["status"] = $status;
|
||||
header("Content-Type: application/json");
|
||||
exit(json_encode($resp));
|
||||
}
|
||||
|
||||
function exitWithJson(array $json) {
|
||||
header("Content-Type: application/json");
|
||||
exit(json_encode($json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API key with most of the characters replaced with *s.
|
||||
* @global string $key
|
||||
* @return string
|
||||
*/
|
||||
function getCensoredKey() {
|
||||
global $key;
|
||||
$resp = $key;
|
||||
if (strlen($key) > 5) {
|
||||
for ($i = 2; $i < strlen($key) - 2; $i++) {
|
||||
$resp[$i] = "*";
|
||||
}
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request is allowed
|
||||
* @global type $VARS
|
||||
* @global type $database
|
||||
* @return bool true if the request should continue, false if the request is bad
|
||||
*/
|
||||
function authenticate(): bool {
|
||||
global $VARS, $database;
|
||||
if (empty($VARS['key'])) {
|
||||
return false;
|
||||
} else {
|
||||
$key = $VARS['key'];
|
||||
if ($database->has('apikeys', ['key' => $key]) !== TRUE) {
|
||||
engageRateLimit();
|
||||
http_response_code(403);
|
||||
Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkVars($vars, $or = false) {
|
||||
global $VARS;
|
||||
$ok = [];
|
||||
foreach ($vars as $key => $val) {
|
||||
if (strpos($key, "OR") === 0) {
|
||||
checkVars($vars[$key], true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only check type of optional variables if they're set, and don't
|
||||
// mark them as bad if they're not set
|
||||
if (strpos($key, " (optional)") !== false) {
|
||||
$key = str_replace(" (optional)", "", $key);
|
||||
if (empty($VARS[$key])) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (empty($VARS[$key])) {
|
||||
$ok[$key] = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($val, "/") === 0) {
|
||||
// regex
|
||||
$ok[$key] = preg_match($val, $VARS[$key]) === 1;
|
||||
} else {
|
||||
$checkmethod = "is_$val";
|
||||
$ok[$key] = !($checkmethod($VARS[$key]) !== true);
|
||||
}
|
||||
}
|
||||
if ($or) {
|
||||
$success = false;
|
||||
$bad = "";
|
||||
foreach ($ok as $k => $v) {
|
||||
if ($v) {
|
||||
$success = true;
|
||||
break;
|
||||
} else {
|
||||
$bad = $k;
|
||||
}
|
||||
}
|
||||
if (!$success) {
|
||||
http_response_code(400);
|
||||
die("400 Bad request: variable $bad is missing or invalid");
|
||||
}
|
||||
} else {
|
||||
foreach ($ok as $key => $bool) {
|
||||
if (!$bool) {
|
||||
http_response_code(400);
|
||||
die("400 Bad request: variable $key is missing or invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the client API key is allowed to access API functions that require the
|
||||
* specified API key type.
|
||||
* @global type $VARS
|
||||
* @global type $database
|
||||
* @param string $type The required key type: "NONE", "AUTH", "READ", or "FULL"
|
||||
* @return bool
|
||||
*/
|
||||
function checkkeytype(string $type): bool {
|
||||
global $VARS, $database;
|
||||
if (empty($VARS['key'])) {
|
||||
return false;
|
||||
} else {
|
||||
$key = $VARS['key'];
|
||||
$keytype = $database->get('apikeys', 'type', ['key' => $key]);
|
||||
$allowedtypes = [];
|
||||
switch ($type) {
|
||||
case "NONE":
|
||||
$allowedtypes = ["NONE", "AUTH", "READ", "FULL"];
|
||||
break;
|
||||
case "AUTH":
|
||||
$allowedtypes = ["AUTH", "READ", "FULL"];
|
||||
break;
|
||||
case "READ":
|
||||
$allowedtypes = ["READ", "FULL"];
|
||||
break;
|
||||
case "FULL":
|
||||
$allowedtypes = ["FULL"];
|
||||
}
|
||||
if (!in_array($type, $allowedtypes)) {
|
||||
http_response_code(403);
|
||||
Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
88
api/index.php
Normal file
88
api/index.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require __DIR__ . '/../required.php';
|
||||
require __DIR__ . '/functions.php';
|
||||
require __DIR__ . '/apisettings.php';
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
$VARS = $_GET;
|
||||
if ($_SERVER['REQUEST_METHOD'] != "GET") {
|
||||
$VARS = array_merge($VARS, $_POST);
|
||||
}
|
||||
|
||||
$requestbody = file_get_contents('php://input');
|
||||
$requestjson = json_decode($requestbody, TRUE);
|
||||
if (json_last_error() == JSON_ERROR_NONE) {
|
||||
$VARS = array_merge($VARS, $requestjson);
|
||||
}
|
||||
|
||||
// If we're not using the old api.php file, allow more flexible requests
|
||||
if (strpos($_SERVER['REQUEST_URI'], "/api.php") === FALSE) {
|
||||
$route = explode("/", substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], "api/") + 4));
|
||||
|
||||
if (count($route) > 1) {
|
||||
$VARS["action"] = $route[0];
|
||||
}
|
||||
if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
|
||||
$VARS["key"] = $route[1];
|
||||
|
||||
for ($i = 2; $i < count($route); $i++) {
|
||||
$key = explode("=", $route[$i], 2)[0];
|
||||
$val = explode("=", $route[$i], 2)[1];
|
||||
$VARS[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($route[count($route) - 1], "?") === 0) {
|
||||
$morevars = explode("&", substr($route[count($route) - 1], 1));
|
||||
foreach ($morevars as $var) {
|
||||
$key = explode("=", $var, 2)[0];
|
||||
$val = explode("=", $var, 2)[1];
|
||||
$VARS[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!authenticate()) {
|
||||
http_response_code(403);
|
||||
die("403 Unauthorized");
|
||||
}
|
||||
|
||||
if (empty($VARS['action'])) {
|
||||
http_response_code(404);
|
||||
die("404 No action specified");
|
||||
}
|
||||
|
||||
if (!isset($APIS[$VARS['action']])) {
|
||||
http_response_code(404);
|
||||
die("404 Action not defined");
|
||||
}
|
||||
|
||||
$APIACTION = $APIS[$VARS["action"]];
|
||||
|
||||
if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) {
|
||||
http_response_code(404);
|
||||
die("404 Action not found");
|
||||
}
|
||||
|
||||
if (!empty($APIACTION["vars"])) {
|
||||
checkVars($APIACTION["vars"]);
|
||||
}
|
||||
|
||||
// Assume we need full API access
|
||||
if (empty($APIACTION["keytype"])) {
|
||||
$APIACTION["keytype"] = "FULL";
|
||||
}
|
||||
|
||||
if (!checkkeytype($APIACTION["keytype"])) {
|
||||
die("403 Unauthorized");
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/actions/" . $APIACTION["load"];
|
222
app.php
Normal file
222
app.php
Normal file
@ -0,0 +1,222 @@
|
||||
<?php
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
// If the SVG/JavaScript version of FontAwesome is needed
|
||||
// Increases overhead by a notable amount
|
||||
define("FONTAWESOME_USEJS", true);
|
||||
|
||||
if ($_SESSION['loggedin'] != true) {
|
||||
header('Location: index.php');
|
||||
die("Session expired. Log in again to continue.");
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/pages.php";
|
||||
|
||||
$pageid = "home";
|
||||
if (!empty($_GET['page'])) {
|
||||
$pg = strtolower($_GET['page']);
|
||||
$pg = preg_replace('/[^0-9a-z_]/', "", $pg);
|
||||
if (array_key_exists($pg, PAGES) && file_exists(__DIR__ . "/pages/" . $pg . ".php")) {
|
||||
$pageid = $pg;
|
||||
} else {
|
||||
$pageid = "404";
|
||||
}
|
||||
}
|
||||
|
||||
header("Link: <static/img/logo.svg>; rel=preload; as=image", false);
|
||||
header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/css/app.css>; rel=preload; as=style", false);
|
||||
if (FONTAWESOME_USEJS) {
|
||||
header("Link: <static/css/svg-with-js.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
|
||||
} else {
|
||||
header("Link: <static/css/fontawesome-all.min.css>; rel=preload; as=style", false);
|
||||
}
|
||||
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
|
||||
header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
<link rel="icon" href="static/img/logo.svg" type="image/svg+xml">
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
<?php
|
||||
if (FONTAWESOME_USEJS) {
|
||||
?>
|
||||
<link href="static/css/svg-with-js.min.css" rel="stylesheet">
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
FontAwesomeConfig = {autoAddCss: false}
|
||||
</script>
|
||||
<script src="static/js/fontawesome-all.min.js"></script>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<link href="static/css/fontawesome-all.min.css" rel="stylesheet">
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
// custom page styles
|
||||
if (isset(PAGES[$pageid]['styles'])) {
|
||||
foreach (PAGES[$pageid]['styles'] as $style) {
|
||||
echo "<link href=\"$style\" rel=\"stylesheet\">\n";
|
||||
header("Link: <$style>; rel=preload; as=style", false);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php
|
||||
// Alert messages
|
||||
if (!empty($_GET['msg'])) {
|
||||
if (array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (empty($_GET['arg'])) {
|
||||
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
|
||||
} else {
|
||||
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
switch (MESSAGES[$_GET['msg']]['type']) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// We don't have a message for this, so just assume an error and escape stuff.
|
||||
$alertmsg = htmlentities($Strings->get($_GET['msg'], false));
|
||||
$alerticon = "times";
|
||||
$alerttype = "danger";
|
||||
}
|
||||
echo <<<END
|
||||
<div class="row justify-content-center" id="msg-alert-box">
|
||||
<div class="col-11 col-sm-6 col-md-5 col-lg-4 col-xl-4">
|
||||
<div class="alert alert-dismissible alert-$alerttype mt-2 p-0 border-0 shadow">
|
||||
<div class="p-2 pl-3">
|
||||
<button type="button" class="close">×</button>
|
||||
<i class="fas fa-$alerticon"></i> $alertmsg
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-$alerttype w-0" id="msg-alert-timeout-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END;
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
// Adjust as needed
|
||||
$navbar_breakpoint = "md";
|
||||
|
||||
// For mobile app
|
||||
echo "<script nonce=\"$SECURE_NONCE\">var navbar_breakpoint = \"$navbar_breakpoint\";</script>"
|
||||
?>
|
||||
<nav class="navbar navbar-expand-<?php echo $navbar_breakpoint; ?> navbar-light bg-orange fixed-top">
|
||||
<button class="navbar-toggler my-0 py-0" type="button" data-toggle="collapse" data-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<!--<i class="fas fa-bars"></i>-->
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand py-0 mr-auto" href="app.php">
|
||||
<img src="static/img/logo.svg" alt="" class="d-none d-<?php echo $navbar_breakpoint; ?>-inline brand-img py-0" />
|
||||
<?php echo $SETTINGS['site_title']; ?>
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse py-0" id="navbar-collapse">
|
||||
<div class="navbar-nav mr-auto py-0">
|
||||
<?php
|
||||
$curpagefound = false;
|
||||
foreach (PAGES as $id => $pg) {
|
||||
if (isset($pg['navbar']) && $pg['navbar'] === TRUE) {
|
||||
if ($pageid == $id) {
|
||||
$curpagefound = true;
|
||||
?>
|
||||
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0 active">
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="app.php?page=<?php echo $id; ?>">
|
||||
<?php
|
||||
if (isset($pg['icon'])) {
|
||||
?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
|
||||
}
|
||||
$Strings->get($pg['title']);
|
||||
?>
|
||||
</a>
|
||||
</span>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="navbar-nav ml-auto py-0" id="navbar-right">
|
||||
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="app.php">
|
||||
<i class="fas fa-user fa-fw"></i><span> <?php echo $_SESSION['realname'] ?></span>
|
||||
</a>
|
||||
</span>
|
||||
<span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
|
||||
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
|
||||
<i class="fas fa-sign-out-alt fa-fw"></i><span> <?php $Strings->get("sign out") ?></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container" id="main-content">
|
||||
<div>
|
||||
<?php
|
||||
include_once __DIR__ . '/pages/' . $pageid . ".php";
|
||||
?>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo $SETTINGS['footer_text']; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo $SETTINGS['copyright']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/jquery-3.3.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="static/js/app.js"></script>
|
||||
<?php
|
||||
// custom page scripts
|
||||
if (isset(PAGES[$pageid]['scripts'])) {
|
||||
foreach (PAGES[$pageid]['scripts'] as $script) {
|
||||
echo "<script src=\"$script\"></script>\n";
|
||||
header("Link: <$script>; rel=preload; as=script", false);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</body>
|
||||
</html>
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
// extra login utils
|
||||
require_once __DIR__ . "/../lib/login.php";
|
||||
|
||||
$APPS["setup_2fa"]["title"] = lang("setup 2fa", false);
|
||||
$APPS["setup_2fa"]["icon"] = "lock";
|
||||
if (userHasTOTP($_SESSION['username'])) {
|
||||
$APPS["setup_2fa"]["content"] = '<a href="action.php?action=rm2fa&source=security" class="btn btn-warning">'
|
||||
. lang("remove 2fa", false) . '</a>';
|
||||
} else {
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa explained", false) . '</div>'
|
||||
. '<button class="btn btn-success">'
|
||||
. lang("enable 2fa", false) . '</button>';
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
$APPS["404_error"]["title"] = lang("404 error", false);
|
||||
$APPS["404_error"]["icon"] = "times";
|
||||
$APPS["404_error"]["type"] = "warning";
|
||||
$APPS["404_error"]["content"] = "<h4>" . lang("page not found", false) . "</h4>";
|
||||
?>
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"manage account security" => "Manage account security",
|
||||
"manage security description" => "Review security features or change your password."
|
||||
]
|
||||
]);
|
||||
$APPS["account_security"]["i18n"] = TRUE;
|
||||
$APPS["account_security"]["title"] = "account security";
|
||||
$APPS["account_security"]["icon"] = "lock";
|
||||
$content = "<p>"
|
||||
. lang("manage security description", false)
|
||||
. '</p> '
|
||||
. '<a href="home.php?page=security" class="btn btn-primary btn-block">'
|
||||
. lang("manage account security", false)
|
||||
. '</a>';
|
||||
$APPS["account_security"]["content"] = $content;
|
||||
?>
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
$APPS["change_password"]["title"] = "Change Password";
|
||||
$APPS["change_password"]["icon"] = "key";
|
||||
$APPS["change_password"]["content"] = <<<CONTENTEND
|
||||
<form action="action.php" method="POST">
|
||||
<input type="password" class="form-control" name="oldpass" placeholder="Current password" />
|
||||
<input type="password" class="form-control" name="newpass" placeholder="New password" />
|
||||
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" />
|
||||
<input type="hidden" name="action" value="chpasswd" />
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<br />
|
||||
<button type="submit" class="btn btn-success btn-sm btn-block">Change Password</button>
|
||||
</form>
|
||||
CONTENTEND;
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"inventory" => "Inventory",
|
||||
"open inventory system" => "Open the inventory system"
|
||||
]
|
||||
]);
|
||||
$APPS["inventory_link"]["i18n"] = TRUE;
|
||||
$APPS["inventory_link"]["title"] = "inventory";
|
||||
$APPS["inventory_link"]["icon"] = "cubes";
|
||||
$content = "<p>" . lang("open inventory system", false) . '</p><a href="' . INVENTORY_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' <i class="fa fa-external-link-square"></i></a>';
|
||||
$APPS["inventory_link"]["content"] = $content;
|
||||
?>
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
// Additional i18n strings
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"sample app" => "Sample Application",
|
||||
]
|
||||
]);
|
||||
// Set to true to automatically parse the app title as a language string.
|
||||
$APPS["sample_app"]["i18n"] = TRUE;
|
||||
// App title.
|
||||
$APPS["sample_app"]["title"] = "sample app";
|
||||
// App icon, from FontAwesome.
|
||||
$APPS["sample_app"]["icon"] = "rocket";
|
||||
// App content.
|
||||
$APPS["sample_app"]["content"] = <<<'CONTENTEND'
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
Item 1
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
Item 2
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
Item 3
|
||||
</div>
|
||||
</div>
|
||||
CONTENTEND;
|
||||
?>
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
|
||||
use OTPHP\Factory;
|
||||
use Endroid\QrCode\QrCode;
|
||||
|
||||
// extra login utils
|
||||
require_once __DIR__ . "/../lib/login.php";
|
||||
|
||||
$APPS["setup_2fa"]["title"] = lang("setup 2fa", false);
|
||||
$APPS["setup_2fa"]["icon"] = "lock";
|
||||
if (userHasTOTP($_SESSION['username'])) {
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa active", false) . '</div>'
|
||||
. '<a href="action.php?action=rm2fa&source=security" class="btn btn-warning btn-sm btn-block">'
|
||||
. lang("remove 2fa", false) . '</a>';
|
||||
} else if ($_GET['2fa'] == "generate") {
|
||||
$codeuri = newTOTP($_SESSION['username']);
|
||||
$qrCode = new QrCode($codeuri);
|
||||
$qrCode->setSize(200);
|
||||
$qrCode->setErrorCorrection("H");
|
||||
$qrcode = $qrCode->getDataUri();
|
||||
$totp = Factory::loadFromProvisioningUri($codeuri);
|
||||
$codesecret = $totp->getSecret();
|
||||
$chunk_secret = trim(chunk_split($codesecret, 8, ' '));
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("scan 2fa qrcode", false) . '</div>' . <<<END
|
||||
<img src="$qrcode" class="img-responsive qrcode" />
|
||||
<div class="well well-sm" style="text-align: center; font-size: 110%; font-family: monospace;">$chunk_secret</div>
|
||||
<form action="action.php" method="POST">
|
||||
<input type="hidden" name="action" value="add2fa" />
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<input type="hidden" name="secret" value="$codesecret" />
|
||||
<button type="submit" class="btn btn-success btn-sm btn-block">
|
||||
END
|
||||
. lang("confirm 2fa", false) . <<<END
|
||||
</button>
|
||||
</form>
|
||||
END;
|
||||
} else {
|
||||
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa explained", false) . '</div>'
|
||||
. '<a class="btn btn-success btn-sm btn-block" href="home.php?page=security&2fa=generate">'
|
||||
. lang("enable 2fa", false) . '</a>';
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"messages" => "Messages",
|
||||
"no messages" => "No messages found."
|
||||
]
|
||||
]);
|
||||
$APPS["taskfloor_messages"]["i18n"] = TRUE;
|
||||
$APPS["taskfloor_messages"]["title"] = "messages";
|
||||
$APPS["taskfloor_messages"]["icon"] = "comments";
|
||||
try {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
|
||||
'action' => "getmsgs",
|
||||
'username' => $_SESSION['username'],
|
||||
'password' => $_SESSION['password'],
|
||||
'max' => 5
|
||||
]]);
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
if (count($resp['messages']) > 0) {
|
||||
$content = '<div class="list-group">';
|
||||
foreach ($resp['messages'] as $msg) {
|
||||
$content .= '<div class="list-group-item">';
|
||||
$content .= $msg['text'];
|
||||
$fromuser = $msg['from']['username'];
|
||||
$fromname = $msg['from']['name'];
|
||||
$touser = $msg['to']['username'];
|
||||
$toname = $msg['to']['name'];
|
||||
$content .= <<<END
|
||||
<br />
|
||||
<span class="small">
|
||||
<span data-toggle="tooltip" title="$fromuser">$fromname</span>
|
||||
<i class="fa fa-caret-right"></i>
|
||||
<span data-toggle="tooltip" title="$touser">$toname</span>
|
||||
</span>
|
||||
END;
|
||||
$content .= '</div>';
|
||||
}
|
||||
$content .= "</div>";
|
||||
} else {
|
||||
$content = "<div class=\"alert alert-info\">" . lang("no messages", false) . "</div>";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
|
||||
}
|
||||
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' <i class="fa fa-external-link-square"></i></a>';
|
||||
$APPS["taskfloor_messages"]["content"] = $content;
|
||||
?>
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
dieifnotloggedin();
|
||||
addMultiLangStrings(["en_us" => [
|
||||
"tasks" => "Tasks",
|
||||
"no tasks found" => "No tasks found."
|
||||
]
|
||||
]);
|
||||
$APPS["taskfloor_tasks"]["i18n"] = TRUE;
|
||||
$APPS["taskfloor_tasks"]["title"] = "tasks";
|
||||
$APPS["taskfloor_tasks"]["icon"] = "tasks";
|
||||
try {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
|
||||
'action' => "gettasks",
|
||||
'username' => $_SESSION['username'],
|
||||
'password' => $_SESSION['password'],
|
||||
'max' => 5
|
||||
]]);
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['status'] == "OK") {
|
||||
if (count($resp['tasks']) > 0) {
|
||||
$content = '<div class="list-group">';
|
||||
foreach ($resp['tasks'] as $task) {
|
||||
$content .= '<div class="list-group-item">';
|
||||
$content .= '<i class="fa fa-fw fa-' . $task['icon'] . '"></i> ' . $task['title'] . '';
|
||||
$content .= '</div>';
|
||||
}
|
||||
$content .= "</div>";
|
||||
} else {
|
||||
$content = "<div class=\"alert alert-success\">" . lang("no tasks found", false) . "</div>";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
|
||||
}
|
||||
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block">' . lang("open app", false) . ' <i class="fa fa-external-link-square"></i></a>';
|
||||
$APPS["taskfloor_tasks"]["content"] = $content;
|
||||
?>
|
@ -1,14 +1,17 @@
|
||||
{
|
||||
"name": "netsyms/business-sso",
|
||||
"description": "Single-sign-on system and accounts database for Netsyms small business apps",
|
||||
"name": "netsyms/accounthub",
|
||||
"description": "Single-sign-on system and dashboard for Netsyms Business Apps",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"catfan/medoo": "^1.2",
|
||||
"catfan/medoo": "^1.7",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"spomky-labs/otphp": "^8.3",
|
||||
"endroid/qrcode": "^1.9",
|
||||
"ldaptools/ldaptools": "^0.24.0",
|
||||
"guzzlehttp/guzzle": "^6.2"
|
||||
"endroid/qr-code": "^3.2",
|
||||
"phpmailer/phpmailer": "^5.2",
|
||||
"christian-riesen/base32": "^1.3",
|
||||
"mibe/feedwriter": "^1.1"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Skylar Ittner",
|
||||
|
1008
composer.lock
generated
1008
composer.lock
generated
File diff suppressed because it is too large
Load Diff
BIN
database.mwb
BIN
database.mwb
Binary file not shown.
442
database.sql
Normal file
442
database.sql
Normal file
@ -0,0 +1,442 @@
|
||||
-- MySQL Script generated by MySQL Workbench
|
||||
-- Mon 11 Feb 2019 04:07:57 PM MST
|
||||
-- Model: New Model Version: 1.0
|
||||
-- MySQL Workbench Forward Engineering
|
||||
|
||||
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
|
||||
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
|
||||
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Schema accounthub
|
||||
-- -----------------------------------------------------
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `acctstatus`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `acctstatus` (
|
||||
`statusid` INT NOT NULL AUTO_INCREMENT,
|
||||
`statuscode` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`statusid`),
|
||||
UNIQUE INDEX `statusid_UNIQUE` (`statusid` ASC),
|
||||
UNIQUE INDEX `statuscode_UNIQUE` (`statuscode` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `accttypes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `accttypes` (
|
||||
`typeid` INT NOT NULL AUTO_INCREMENT,
|
||||
`typecode` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`typeid`),
|
||||
UNIQUE INDEX `typeid_UNIQUE` (`typeid` ASC),
|
||||
UNIQUE INDEX `typecode_UNIQUE` (`typecode` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `accounts`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `accounts` (
|
||||
`uid` INT NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(190) NOT NULL,
|
||||
`password` VARCHAR(255) NULL,
|
||||
`realname` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(255) NULL DEFAULT 'NOEMAIL@EXAMPLE.COM',
|
||||
`authsecret` VARCHAR(100) NULL,
|
||||
`pin` VARCHAR(10) NULL,
|
||||
`phone1` VARCHAR(45) NOT NULL,
|
||||
`phone2` VARCHAR(45) NOT NULL,
|
||||
`acctstatus` INT NOT NULL DEFAULT 0,
|
||||
`accttype` INT NOT NULL,
|
||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`uid`, `username`),
|
||||
UNIQUE INDEX `userid_UNIQUE` (`uid` ASC),
|
||||
UNIQUE INDEX `username_UNIQUE` (`username` ASC),
|
||||
INDEX `fk_accounts_acctstatus_idx` (`acctstatus` ASC),
|
||||
INDEX `email_index` (`email` ASC),
|
||||
INDEX `fk_accounts_accttypes1_idx` (`accttype` ASC),
|
||||
CONSTRAINT `fk_accounts_acctstatus`
|
||||
FOREIGN KEY (`acctstatus`)
|
||||
REFERENCES `acctstatus` (`statusid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_accounts_accttypes1`
|
||||
FOREIGN KEY (`accttype`)
|
||||
REFERENCES `accttypes` (`typeid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apikeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `apikeys` (
|
||||
`key` VARCHAR(60) NOT NULL,
|
||||
`notes` TEXT NULL,
|
||||
`type` VARCHAR(45) NOT NULL DEFAULT 'FULL',
|
||||
PRIMARY KEY (`key`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `groups`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `groups` (
|
||||
`groupid` INT NOT NULL AUTO_INCREMENT,
|
||||
`groupname` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`groupid`),
|
||||
UNIQUE INDEX `groupid_UNIQUE` (`groupid` ASC),
|
||||
UNIQUE INDEX `groupname_UNIQUE` (`groupname` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `assigned_groups`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `assigned_groups` (
|
||||
`groupid` INT NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
PRIMARY KEY (`groupid`, `uid`),
|
||||
INDEX `fk_groups_has_accounts_accounts1_idx` (`uid` ASC),
|
||||
INDEX `fk_groups_has_accounts_groups1_idx` (`groupid` ASC),
|
||||
CONSTRAINT `fk_groups_has_accounts_groups1`
|
||||
FOREIGN KEY (`groupid`)
|
||||
REFERENCES `groups` (`groupid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_groups_has_accounts_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `managers`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `managers` (
|
||||
`managerid` INT NOT NULL,
|
||||
`employeeid` INT NOT NULL,
|
||||
PRIMARY KEY (`managerid`, `employeeid`),
|
||||
INDEX `fk_managers_accounts2_idx` (`employeeid` ASC),
|
||||
CONSTRAINT `fk_managers_accounts1`
|
||||
FOREIGN KEY (`managerid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_managers_accounts2`
|
||||
FOREIGN KEY (`employeeid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `logtypes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `logtypes` (
|
||||
`logtype` INT NOT NULL,
|
||||
`typename` VARCHAR(45) NULL,
|
||||
PRIMARY KEY (`logtype`),
|
||||
UNIQUE INDEX `logtype_UNIQUE` (`logtype` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `authlog`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `authlog` (
|
||||
`logid` INT NOT NULL AUTO_INCREMENT,
|
||||
`logtime` DATETIME NOT NULL,
|
||||
`logtype` INT NOT NULL,
|
||||
`uid` INT NULL,
|
||||
`ip` VARCHAR(45) NULL,
|
||||
`otherdata` VARCHAR(255) NULL,
|
||||
PRIMARY KEY (`logid`),
|
||||
UNIQUE INDEX `logid_UNIQUE` (`logid` ASC),
|
||||
INDEX `fk_authlog_logtypes1_idx` (`logtype` ASC),
|
||||
INDEX `fk_authlog_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_authlog_logtypes1`
|
||||
FOREIGN KEY (`logtype`)
|
||||
REFERENCES `logtypes` (`logtype`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_authlog_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `permissions`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `permissions` (
|
||||
`permid` INT NOT NULL AUTO_INCREMENT,
|
||||
`permcode` VARCHAR(45) NOT NULL,
|
||||
`perminfo` VARCHAR(200) NULL,
|
||||
PRIMARY KEY (`permid`),
|
||||
UNIQUE INDEX `permid_UNIQUE` (`permid` ASC))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `assigned_permissions`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `assigned_permissions` (
|
||||
`uid` INT NOT NULL,
|
||||
`permid` INT NOT NULL,
|
||||
PRIMARY KEY (`uid`, `permid`),
|
||||
INDEX `fk_permissions_has_accounts_accounts1_idx` (`uid` ASC),
|
||||
INDEX `fk_permissions_has_accounts_permissions1_idx` (`permid` ASC),
|
||||
CONSTRAINT `fk_permissions_has_accounts_permissions1`
|
||||
FOREIGN KEY (`permid`)
|
||||
REFERENCES `permissions` (`permid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_permissions_has_accounts_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `mobile_codes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `mobile_codes` (
|
||||
`codeid` INT NOT NULL AUTO_INCREMENT,
|
||||
`uid` INT NOT NULL,
|
||||
`code` VARCHAR(45) NOT NULL DEFAULT '',
|
||||
`description` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`codeid`),
|
||||
UNIQUE INDEX `codeid_UNIQUE` (`codeid` ASC),
|
||||
INDEX `fk_mobile_codes_accounts1_idx` (`uid` ASC),
|
||||
UNIQUE INDEX `code_UNIQUE` (`code` ASC),
|
||||
CONSTRAINT `fk_mobile_codes_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `rate_limit`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `rate_limit` (
|
||||
`ipaddr` VARCHAR(45) NOT NULL,
|
||||
`lastaction` DATETIME NULL,
|
||||
PRIMARY KEY (`ipaddr`))
|
||||
ENGINE = MEMORY;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `onetimekeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `onetimekeys` (
|
||||
`key` VARCHAR(10) NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
`expires` DATETIME NOT NULL,
|
||||
INDEX `fk_onetimekeys_accounts1_idx` (`uid` ASC),
|
||||
PRIMARY KEY (`key`),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
CONSTRAINT `fk_onetimekeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `notifications`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `notifications` (
|
||||
`notificationid` INT NOT NULL AUTO_INCREMENT,
|
||||
`uid` INT NOT NULL,
|
||||
`timestamp` DATETIME NOT NULL,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`content` TINYTEXT NOT NULL,
|
||||
`url` VARCHAR(255) NOT NULL,
|
||||
`seen` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`sensitive` TINYINT(1) NOT NULL,
|
||||
`appid` VARCHAR(255) NULL,
|
||||
PRIMARY KEY (`notificationid`, `uid`),
|
||||
UNIQUE INDEX `notificationid_UNIQUE` (`notificationid` ASC),
|
||||
INDEX `fk_notifications_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_notifications_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `userkeytypes`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `userkeytypes` (
|
||||
`typeid` INT NOT NULL,
|
||||
`typename` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`typeid`, `typename`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `userkeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `userkeys` (
|
||||
`uid` INT NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`created` DATETIME NULL,
|
||||
`typeid` INT NOT NULL,
|
||||
PRIMARY KEY (`uid`),
|
||||
INDEX `fk_userkeys_userkeytypes1_idx` (`typeid` ASC),
|
||||
CONSTRAINT `fk_userkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_userkeys_userkeytypes1`
|
||||
FOREIGN KEY (`typeid`)
|
||||
REFERENCES `userkeytypes` (`typeid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `userloginkeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `userloginkeys` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`key` VARCHAR(255) NOT NULL,
|
||||
`expires` DATETIME NULL,
|
||||
`uid` INT NULL,
|
||||
`appname` VARCHAR(255) NOT NULL,
|
||||
`appicon` TINYTEXT NULL,
|
||||
PRIMARY KEY (`id`, `key`),
|
||||
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
INDEX `fk_userloginkeys_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_userloginkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apppasswords`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `apppasswords` (
|
||||
`passid` INT NOT NULL AUTO_INCREMENT,
|
||||
`hash` VARCHAR(255) NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`passid`, `uid`),
|
||||
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
|
||||
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_apppasswords_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE;
|
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `acctstatus`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (1, 'NORMAL');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (2, 'LOCKED_OR_DISABLED');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (3, 'CHANGE_PASSWORD');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (4, 'TERMINATED');
|
||||
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (5, 'ALERT_ON_ACCESS');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `accttypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (1, 'LOCAL');
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (2, 'EXTERNAL');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `logtypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (1, 'PORTAL_LOGIN_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (2, 'PORTAL_LOGIN_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (3, 'PASSWORD_CHANGED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (4, 'API_LOGIN_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (5, 'API_LOGIN_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (6, 'PORTAL_BAD_AUTHCODE');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (7, 'API_BAD_AUTHCODE');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (8, 'BAD_CAPTCHA');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (9, '2FA_ADDED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (10, '2FA_REMOVED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (11, 'PORTAL_LOGOUT');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (12, 'API_AUTH_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (13, 'API_AUTH_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (14, 'API_BAD_KEY');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (15, 'LOG_CLEARED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (16, 'USER_REMOVED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (17, 'USER_ADDED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (18, 'USER_EDITED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (19, 'MOBILE_LOGIN_OK');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (20, 'MOBILE_LOGIN_FAILED');
|
||||
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (21, 'MOBILE_BAD_KEY');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `permissions`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (1, 'ADMIN', 'System administrator');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (100, 'INV_VIEW', 'Access inventory system');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (101, 'INV_EDIT', 'Edit inventory system');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (200, 'TASKFLOOR', 'Access TaskFloor');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (300, 'QWIKCLOCK', 'Access QwikClock and punch in/out');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (301, 'QWIKCLOCK_MANAGE', 'Edit punches and other data for managed users');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (302, 'QWIKCLOCK_EDITSELF', 'Edit own punches and other data');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (303, 'QWIKCLOCK_ADMIN', 'Add and edit shifts and other data for all users');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (400, 'SITEWRITER', 'Manage and edit websites, messages, and analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (401, 'SITEWRITER_CONTACT', 'Manage messages sent via website contact forms');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (402, 'SITEWRITER_ANALYTICS', 'View website analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (403, 'SITEWRITER_EDIT', 'Edit website content');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEWRITER_FILES', 'Manage and upload files');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `userkeytypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');
|
||||
|
||||
COMMIT;
|
||||
|
54
database_upgrade/1.0.1_2.0.sql
Normal file
54
database_upgrade/1.0.1_2.0.sql
Normal file
@ -0,0 +1,54 @@
|
||||
CREATE TABLE IF NOT EXISTS `onetimekeys` (
|
||||
`key` VARCHAR(10) NOT NULL,
|
||||
`uid` INT(11) NOT NULL,
|
||||
`expires` DATETIME NOT NULL,
|
||||
INDEX `fk_onetimekeys_accounts1_idx` (`uid` ASC),
|
||||
PRIMARY KEY (`key`),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
CONSTRAINT `fk_onetimekeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounthub`.`accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `notifications` (
|
||||
`notificationid` INT NOT NULL AUTO_INCREMENT,
|
||||
`uid` INT NOT NULL,
|
||||
`timestamp` DATETIME NOT NULL,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`content` TINYTEXT NOT NULL,
|
||||
`url` VARCHAR(255) NOT NULL,
|
||||
`seen` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`sensitive` TINYINT(1) NOT NULL,
|
||||
`appid` VARCHAR(255) NULL,
|
||||
PRIMARY KEY (`notificationid`, `uid`),
|
||||
UNIQUE INDEX `notificationid_UNIQUE` (`notificationid` ASC),
|
||||
INDEX `fk_notifications_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_notifications_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
ALTER TABLE `groups`
|
||||
CHANGE COLUMN `groupid` `groupid` INT(11) NOT NULL AUTO_INCREMENT;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
ALTER TABLE `accounts`
|
||||
ADD COLUMN `pin` VARCHAR(10) NULL DEFAULT NULL AFTER `authsecret`;
|
||||
|
||||
ALTER TABLE `mobile_codes`
|
||||
CHANGE COLUMN `code` `code` VARCHAR(45) NOT NULL DEFAULT '',
|
||||
ADD COLUMN `description` VARCHAR(255) NOT NULL DEFAULT '' AFTER `code`;
|
||||
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (400, 'SITEWRITER', 'Manage and edit websites, messages, and analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (401, 'SITEWRITER_CONTACT', 'Manage messages sent via website contact forms');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (402, 'SITEWRITER_ANALYTICS', 'View website analytics');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (403, 'SITEWRITER_EDIT', 'Edit website content');
|
||||
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEWRITER_FILES', 'Manage and upload files');
|
35
database_upgrade/2.0_2.1.sql
Normal file
35
database_upgrade/2.0_2.1.sql
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `userkeys` (
|
||||
`uid` INT(11) NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`created` DATETIME NULL DEFAULT NULL,
|
||||
`typeid` INT(11) NOT NULL,
|
||||
PRIMARY KEY (`uid`),
|
||||
INDEX `fk_userkeys_userkeytypes1_idx` (`typeid` ASC),
|
||||
CONSTRAINT `fk_userkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounthub`.`accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_userkeys_userkeytypes1`
|
||||
FOREIGN KEY (`typeid`)
|
||||
REFERENCES `accounthub`.`userkeytypes` (`typeid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `userkeytypes` (
|
||||
`typeid` INT(11) NOT NULL,
|
||||
`typename` VARCHAR(45) NOT NULL,
|
||||
PRIMARY KEY (`typeid`, `typename`))
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');
|
48
database_upgrade/2.1_2.2.sql
Normal file
48
database_upgrade/2.1_2.2.sql
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
DROP TABLE IF EXISTS `available_apps`;
|
||||
DROP TABLE IF EXISTS `apps`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `userloginkeys` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`key` VARCHAR(255) NOT NULL,
|
||||
`expires` DATETIME NULL DEFAULT NULL,
|
||||
`uid` INT(11) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`, `key`),
|
||||
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
|
||||
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
|
||||
INDEX `fk_userloginkeys_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_userloginkeys_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
||||
|
||||
ALTER TABLE `userloginkeys`
|
||||
ADD COLUMN `appname` VARCHAR(255) NOT NULL AFTER `uid`;
|
||||
ALTER TABLE `userloginkeys`
|
||||
ADD COLUMN `appicon` TINYTEXT NULL DEFAULT NULL AFTER `appname`;
|
||||
ALTER TABLE `apikeys`
|
||||
ADD COLUMN `type` VARCHAR(45) NOT NULL DEFAULT 'FULL' AFTER `notes`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `apppasswords` (
|
||||
`passid` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`hash` VARCHAR(255) NOT NULL,
|
||||
`uid` INT(11) NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`passid`, `uid`),
|
||||
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
|
||||
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_apppasswords_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
89
feed.php
Normal file
89
feed.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require __DIR__ . "/required.php";
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
use \FeedWriter\RSS1;
|
||||
use \FeedWriter\RSS2;
|
||||
use \FeedWriter\ATOM;
|
||||
|
||||
if (empty($_GET['key']) || empty($_GET['type'])) {
|
||||
http_response_code(400);
|
||||
die("400 Bad Request: please send a user key and a feed type");
|
||||
}
|
||||
|
||||
if (!$database->has('userkeys', ['key' => $_GET['key']])) {
|
||||
http_response_code(403);
|
||||
die("403 Forbidden: provide valid key");
|
||||
}
|
||||
|
||||
$uid = $database->get('userkeys', 'uid', ['key' => $_GET['key']]);
|
||||
$user = new User($uid);
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::NORMAL:
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
case AccountStatus::ALERT_ON_ACCESS:
|
||||
break;
|
||||
default:
|
||||
http_response_code(403);
|
||||
die("403 Forbidden: user account not active");
|
||||
}
|
||||
|
||||
$notifications = Notifications::get($user);
|
||||
|
||||
switch ($_GET['type']) {
|
||||
case "rss1":
|
||||
$feed = new RSS1();
|
||||
break;
|
||||
case "rss":
|
||||
case "rss2":
|
||||
$feed = new RSS2();
|
||||
break;
|
||||
case "atom":
|
||||
$feed = new ATOM();
|
||||
break;
|
||||
default:
|
||||
http_response_code(400);
|
||||
die("400 Bad Request: feed parameter must have a value of \"rss\", \"rss1\", \"rss2\" or \"atom\".");
|
||||
}
|
||||
|
||||
$feed->setTitle($Strings->build("Notifications from server for user", ['server' => $SETTINGS['site_title'], 'user' => $user->getName()], false));
|
||||
|
||||
if (strpos($SETTINGS['url'], "http") === 0) {
|
||||
$url = $SETTINGS['url'];
|
||||
} else {
|
||||
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'];
|
||||
}
|
||||
|
||||
$feed->setLink($url);
|
||||
|
||||
foreach ($notifications as $n) {
|
||||
$item = $feed->createNewItem();
|
||||
$item->setTitle($n['title']);
|
||||
if (empty($n['url'])) {
|
||||
$item->setLink($url);
|
||||
} else {
|
||||
$item->setLink($n['url']);
|
||||
}
|
||||
$item->setDate(strtotime($n['timestamp']));
|
||||
if ($n['sensitive']) {
|
||||
$content = $Strings->get("Sensitive content hidden", false);
|
||||
} else {
|
||||
$content = $n['content'];
|
||||
}
|
||||
if ($_GET['type'] == "atom") {
|
||||
$item->setContent($content);
|
||||
} else {
|
||||
$item->setDescription($content);
|
||||
}
|
||||
$feed->addItem($item);
|
||||
}
|
||||
|
||||
$feed->printFeed();
|
182
home.php
182
home.php
@ -1,180 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/required.php";
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
if ($_SESSION['loggedin'] != true) {
|
||||
header('Location: index.php');
|
||||
die("Session expired. Log in again to continue.");
|
||||
} else if (is_empty($_SESSION['password'])) {
|
||||
header('Location: index.php');
|
||||
die("You need to log in again.");
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/pages.php";
|
||||
|
||||
$pageid = "home";
|
||||
if (!is_empty($_GET['page'])) {
|
||||
if (array_key_exists($_GET['page'], PAGES)) {
|
||||
$pageid = $_GET['page'];
|
||||
} else {
|
||||
$pageid = "404";
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo SITE_TITLE; ?></title>
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||
<img class="img-responsive banner-image" src="static/img/logo.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navbar navbar-inverse">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<?php
|
||||
if (PAGES[$pageid]['title'] == "{DEFAULT}") {
|
||||
?>
|
||||
<span class="navbar-brand">
|
||||
<?php
|
||||
lang2("welcome user", ["user" => $_SESSION['realname']]);
|
||||
?>
|
||||
</span>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<a class="navbar-brand" href="home.php?page=home">
|
||||
<?php
|
||||
// add breadcrumb thing
|
||||
lang("home");
|
||||
echo " <i class=\"fa fa-caret-right\"></i> ";
|
||||
lang(PAGES[$pageid]['title']);
|
||||
?>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-gears fa-fw"></i> <?php lang("options") ?> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="home.php?page=security"><i class="fa fa-lock fa-fw"></i> <?php lang("account security") ?></a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="action.php?action=signout"><i class="fa fa-sign-out fa-fw"></i> <?php lang("sign out") ?></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php
|
||||
// Alert messages
|
||||
if (!is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (is_empty($_GET['arg'])) {
|
||||
$alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
|
||||
} else {
|
||||
$alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => $_GET['arg']], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
switch (MESSAGES[$_GET['msg']]['type']) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
}
|
||||
echo <<<END
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||
<div class="alert alert-dismissible alert-$alerttype">
|
||||
<button type="button" class="close">×</button>
|
||||
<i class="fa fa-$alerticon"></i> $alertmsg
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END;
|
||||
}
|
||||
?>
|
||||
<div class="row">
|
||||
<?php
|
||||
// Center the widgets horizontally on the screen
|
||||
$appcount = count(APPS[$pageid]);
|
||||
if ($appcount == 1) {
|
||||
?>
|
||||
<div class="hidden-xs col-sm-3 col-md-4 col-lg-4">
|
||||
<!-- Empty placeholder column for nice center-align -->
|
||||
</div>
|
||||
<?php
|
||||
} else if ($appcount == 2) {
|
||||
?>
|
||||
<div class="hidden-xs hidden-sm col-md-2 col-lg-2">
|
||||
<!-- Empty placeholder column for nice center-align -->
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Load app widgets
|
||||
foreach (APPS[$pageid] as $app) {
|
||||
if (file_exists(__DIR__ . "/apps/" . $app . ".php")) {
|
||||
include_once __DIR__ . "/apps/" . $app . ".php";
|
||||
$apptitle = ($APPS[$app]['i18n'] === TRUE ? lang($APPS[$app]['title'], false) : $APPS[$app]['title']);
|
||||
$appicon = (is_empty($APPS[$app]['icon']) ? "" : "fa fa-fw fa-" . $APPS[$app]['icon']);
|
||||
$apptype = (is_empty($APPS[$app]['type']) ? "default" : $APPS[$app]['type']);
|
||||
$appcontent = $APPS[$app]['content'];
|
||||
echo <<<END
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
|
||||
<div class="panel panel-$apptype">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><i class="$appicon"></i> $apptitle </h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
$appcontent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo LICENSE_TEXT; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/jquery-3.2.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
<script src="static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
header("Location: app.php");
|
313
index.php
313
index.php
@ -1,226 +1,113 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . "/required.php";
|
||||
|
||||
require_once __DIR__ . "/lib/login.php";
|
||||
|
||||
// If we're logged in, we don't need to be here.
|
||||
if ($_SESSION['loggedin'] && !is_empty($_SESSION['password'])) {
|
||||
header('Location: home.php');
|
||||
// if we're logged in, we don't need to be here.
|
||||
if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
|
||||
header('Location: app.php');
|
||||
die();
|
||||
// This branch will likely run if the user signed in from a different app.
|
||||
} else if ($_SESSION['loggedin'] && is_empty($_SESSION['password'])) {
|
||||
$alert = lang("sign in again", false);
|
||||
$alerttype = "info";
|
||||
}
|
||||
|
||||
/* Authenticate user */
|
||||
$username_ok = false;
|
||||
$multiauth = false;
|
||||
$change_password = false;
|
||||
if ($VARS['progress'] == "1") {
|
||||
if (!RECAPTCHA_ENABLED || (RECAPTCHA_ENABLED && verifyReCaptcha($VARS['g-recaptcha-response']))) {
|
||||
$autherror = "";
|
||||
if (user_exists($VARS['username'])) {
|
||||
$status = get_account_status($VARS['username'], $error);
|
||||
switch ($status) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
$alert = lang("account locked", false);
|
||||
break;
|
||||
case "TERMINATED":
|
||||
$alert = lang("account terminated", false);
|
||||
break;
|
||||
case "CHANGE_PASSWORD":
|
||||
$alert = lang("password expired", false);
|
||||
$alerttype = "info";
|
||||
$_SESSION['username'] = strtolower($VARS['username']);
|
||||
$change_password = true;
|
||||
break;
|
||||
case "NORMAL":
|
||||
$username_ok = true;
|
||||
break;
|
||||
case "ALERT_ON_ACCESS":
|
||||
sendLoginAlertEmail($VARS['username']);
|
||||
$username_ok = true;
|
||||
break;
|
||||
default:
|
||||
if (!is_empty($error)) {
|
||||
$alert = $error;
|
||||
break;
|
||||
}
|
||||
$alert = lang("login error", false);
|
||||
break;
|
||||
}
|
||||
if ($username_ok) {
|
||||
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
|
||||
$_SESSION['passok'] = true; // stop logins using only username and authcode
|
||||
if (userHasTOTP($VARS['username'])) {
|
||||
$multiauth = true;
|
||||
$_SESSION['password'] = $VARS['password'];
|
||||
} else {
|
||||
doLoginUser($VARS['username'], $VARS['password']);
|
||||
insertAuthLog(1, $_SESSION['uid']);
|
||||
header('Location: home.php');
|
||||
die("Logged in, go to home.php");
|
||||
}
|
||||
} else {
|
||||
if (!is_empty($autherror)) {
|
||||
$alert = $autherror;
|
||||
insertAuthLog(2, null, "Username: " . $VARS['username']);
|
||||
} else {
|
||||
$alert = lang("login incorrect", false);
|
||||
insertAuthLog(2, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // User does not exist anywhere
|
||||
$alert = lang("login incorrect", false);
|
||||
insertAuthLog(2, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else {
|
||||
$alert = lang("captcha error", false);
|
||||
insertAuthLog(8, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else if ($VARS['progress'] == "2") {
|
||||
if ($_SESSION['passok'] !== true) {
|
||||
// stop logins using only username and authcode
|
||||
sendError("Password integrity check failed!");
|
||||
}
|
||||
if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
|
||||
doLoginUser($VARS['username'], $VARS['password']);
|
||||
insertAuthLog(1, $_SESSION['uid']);
|
||||
header('Location: home.php');
|
||||
die("Logged in, go to home.php");
|
||||
} else {
|
||||
$alert = lang("2fa incorrect", false);
|
||||
insertAuthLog(6, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else if ($VARS['progress'] == "chpasswd") {
|
||||
if (!is_empty($_SESSION['username'])) {
|
||||
$error = [];
|
||||
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
|
||||
if ($result === TRUE) {
|
||||
$alert = lang(MESSAGES["password_updated"]["string"], false);
|
||||
$alerttype = MESSAGES["password_updated"]["type"];
|
||||
}
|
||||
switch (count($error)) {
|
||||
case 1:
|
||||
$alert = lang(MESSAGES[$error[0]]["string"], false);
|
||||
$alerttype = MESSAGES[$error[0]]["type"];
|
||||
break;
|
||||
case 2:
|
||||
$alert = lang2(MESSAGES[$error[0]]["string"], ["arg" => $error[1]], false);
|
||||
$alerttype = MESSAGES[$error[0]]["type"];
|
||||
break;
|
||||
default:
|
||||
$alert = lang(MESSAGES["generic_op_error"]["string"], false);
|
||||
$alerttype = MESSAGES["generic_op_error"]["type"];
|
||||
}
|
||||
} else {
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
die();
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo SITE_TITLE; ?></title>
|
||||
/**
|
||||
* Show a simple HTML page with a line of text and a button. Matches the UI of
|
||||
* the AccountHub login flow.
|
||||
*
|
||||
* @global type $SETTINGS
|
||||
* @global type $SECURE_NONCE
|
||||
* @global type $Strings
|
||||
* @param string $title Text to show, passed through i18n
|
||||
* @param string $button Button text, passed through i18n
|
||||
* @param string $url URL for the button
|
||||
*/
|
||||
function showHTML(string $title, string $button, string $url) {
|
||||
global $SETTINGS, $SECURE_NONCE, $Strings;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="static/css/app.css" rel="stylesheet">
|
||||
<?php if (RECAPTCHA_ENABLED) { ?>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
<?php } ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||
<div>
|
||||
<img class="img-responsive banner-image" src="static/img/logo.svg" />
|
||||
</div>
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><?php lang("sign in"); ?></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="" method="POST">
|
||||
<?php
|
||||
if (!is_empty($alert)) {
|
||||
$alerttype = isset($alerttype) ? $alerttype : "danger";
|
||||
?>
|
||||
<div class="alert alert-<?php echo $alerttype ?>">
|
||||
<?php
|
||||
switch ($alerttype) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
default:
|
||||
$alerticon = "square-o";
|
||||
}
|
||||
?>
|
||||
<i class="fa fa-fw fa-<?php echo $alerticon ?>"></i> <?php echo $alert ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
if (!$multiauth && !$change_password) {
|
||||
?>
|
||||
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autofocus /><br />
|
||||
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" /><br />
|
||||
<?php if (RECAPTCHA_ENABLED) { ?>
|
||||
<div class="g-recaptcha" data-sitekey="<?php echo RECAPTCHA_SITE_KEY; ?>"></div>
|
||||
<br />
|
||||
<?php } ?>
|
||||
<input type="hidden" name="progress" value="1" />
|
||||
<?php
|
||||
} else if ($multiauth) {
|
||||
?>
|
||||
<div class="alert alert-info">
|
||||
<?php lang("2fa prompt"); ?>
|
||||
</div>
|
||||
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autofocus /><br />
|
||||
<input type="hidden" name="progress" value="2" />
|
||||
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
|
||||
<?php
|
||||
} else if ($change_password) {
|
||||
?>
|
||||
<input type="password" class="form-control" name="oldpass" placeholder="Current password" required="required" autocomplete="new-password" autofocus /><br />
|
||||
<input type="password" class="form-control" name="newpass" placeholder="New password" required="required" autocomplete="off" /><br />
|
||||
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" required="required" autocomplete="off" /><br />
|
||||
<input type="hidden" name="progress" value="chpasswd" />
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<?php lang("continue"); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<link rel="icon" href="static/img/logo.svg">
|
||||
|
||||
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
.display-5 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
max-height: 100px;
|
||||
margin: 2em auto;
|
||||
border: 1px solid grey;
|
||||
border-radius: 15%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 text-center">
|
||||
<img class="banner-image" src="./static/img/logo.svg" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-8 col-lg-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<a href="<?php echo $url; ?>" class="btn btn-primary btn-block"><?php $Strings->get($button); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo LICENSE_TEXT; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/jquery-3.2.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if (!empty($_GET['logout'])) {
|
||||
showHTML("You have been logged out.", "Log in again", "./index.php");
|
||||
die();
|
||||
}
|
||||
if (empty($_SESSION["login_code"])) {
|
||||
$redirecttologin = true;
|
||||
} else {
|
||||
try {
|
||||
$uid = LoginKey::getuid($_SESSION["login_code"]);
|
||||
|
||||
$user = new User($uid);
|
||||
Session::start($user);
|
||||
$_SESSION["login_code"] = null;
|
||||
header('Location: app.php');
|
||||
showHTML("Logged in", "Continue", "./app.php");
|
||||
die();
|
||||
} catch (Exception $ex) {
|
||||
$redirecttologin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($redirecttologin) {
|
||||
try {
|
||||
$code = LoginKey::generate($SETTINGS["site_title"], "../static/img/logo.svg");
|
||||
|
||||
$_SESSION["login_code"] = $code;
|
||||
|
||||
$loginurl = "./login/?code=" . htmlentities($code) . "&redirect=" . htmlentities($_SERVER["REQUEST_URI"]);
|
||||
|
||||
header("Location: $loginurl");
|
||||
showHTML("Continue", "Continue", $loginurl);
|
||||
die();
|
||||
} catch (Exception $ex) {
|
||||
sendError($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
$STRINGS = [
|
||||
"sign in" => "Sign In",
|
||||
"username" => "Username",
|
||||
"password" => "Password",
|
||||
"continue" => "Continue",
|
||||
"authcode" => "Authentication code",
|
||||
"2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
|
||||
"2fa incorrect" => "Authentication code incorrect.",
|
||||
"login incorrect" => "Login incorrect.",
|
||||
"login successful" => "Login successful.",
|
||||
"login error" => "There was a server problem. Try again later.",
|
||||
"account locked" => "This account has been disabled. Contact technical support.",
|
||||
"password expired" => "You must change your password before continuing.",
|
||||
"account terminated" => "Account terminated. Access denied.",
|
||||
"account state error" => "Your account state is not stable. Log out, restart your browser, and try again.",
|
||||
"password on 500 list" => "The given password is ranked number {arg} out of the 500 most common passwords. Try a different one.",
|
||||
"welcome user" => "Welcome, {user}!",
|
||||
"change password" => "Change password",
|
||||
"security options" => "Security options",
|
||||
"account security" => "Account security",
|
||||
"sign out" => "Sign out",
|
||||
"settings" => "Settings",
|
||||
"options" => "Options",
|
||||
"404 error" => "404 Error",
|
||||
"page not found" => "Page not found.",
|
||||
"current password incorrect" => "The current password is incorrect. Try again.",
|
||||
"new password mismatch" => "The new passwords did not match. Try again.",
|
||||
"weak password" => "Password does not meet requirements.",
|
||||
"password updated" => "Password updated successfully.",
|
||||
"setup 2fa" => "Setup 2-factor authentication",
|
||||
"2fa removed" => "2-factor authentication disabled.",
|
||||
"2fa enabled" => "2-factor authentication activated.",
|
||||
"remove 2fa" => "Disable 2-factor authentication",
|
||||
"2fa explained" => "2-factor authentication adds more security to your account. You'll need an app such as Google Authenticator on your smartphone. When you have the app installed, you can enable 2-factor authentication by clicking the button below and scanning a QR code with the app. Whenever you sign in in the future, you'll need to input a six-digit code from your phone into the login page when prompted. You can disable 2-factor authentication from this page if you change your mind.",
|
||||
"2fa active" => "2-factor authentication is active on your account. To remove 2fa, reset your authentication secret, or change to a new security device, click the button below.",
|
||||
"enable 2fa" => "Enable 2-factor authentication",
|
||||
"scan 2fa qrcode" => "Scan the QR Code with the authenticator app, or enter the secret key manually.",
|
||||
"confirm 2fa" => "Finish setup",
|
||||
"invalid parameters" => "Invalid request parameters.",
|
||||
"ldap server error" => "The LDAP server returned an error: {arg}",
|
||||
"user does not exist" => "User does not exist.",
|
||||
"captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.",
|
||||
"home" => "Home",
|
||||
"ldap error" => "LDAP error: {error}",
|
||||
"old and new passwords match" => "Your current and new passwords are the same.",
|
||||
"generic op error" => "An unknown error occurred. Try again later.",
|
||||
"password complexity insufficent" => "The new password does not meet the minumum requirements defined by your system administrator.",
|
||||
"error loading widget" => "There was a problem loading this app.",
|
||||
"open app" => "Open App",
|
||||
"sign in again" => "Please sign in again to continue."
|
||||
];
|
17
langs/en/2fa.json
Normal file
17
langs/en/2fa.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"setup 2fa": "Setup 2-factor authentication",
|
||||
"2fa removed": "2-factor authentication disabled.",
|
||||
"2fa enabled": "2-factor authentication activated.",
|
||||
"remove 2fa": "Disable 2-factor authentication",
|
||||
"2fa explained": "2-factor authentication adds more security to your account. You can use the Auth Keys (key icon) feature of the Netsyms mobile app, or another TOTP-enabled app (Authy, FreeOTP, etc) on your smartphone. When you have the app installed, you can enable 2-factor authentication by clicking the button below and scanning a QR code with the app. Whenever you sign in in the future, you'll need to input a six-digit code from your phone into the login page when prompted. You can disable 2-factor authentication from this page if you change your mind.",
|
||||
"2fa active": "2-factor authentication is active on your account. To remove 2fa, reset your authentication secret, or change to a new security device, click the button below.",
|
||||
"enable 2fa": "Enable 2-factor authentication",
|
||||
"scan 2fa qrcode": "Scan the QR Code with the authenticator app, or enter the information manually. Then type in the six-digit code the app gives you and press Finish Setup.",
|
||||
"confirm 2fa": "Finish setup",
|
||||
"enter otp code": "Enter 6-digit code",
|
||||
"secret key": "Secret key",
|
||||
"label": "Label",
|
||||
"issuer": "Issuer",
|
||||
"no such code or code expired": "That code is incorrect or expired.",
|
||||
"2-factor is enabled, you need to use the QR code or manual setup for security reasons": "2-factor is enabled, you need to use the QR code or manual setup for security reasons."
|
||||
}
|
5
langs/en/api.json
Normal file
5
langs/en/api.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"user does not exist": "User does not exist.",
|
||||
"group does not exist": "Group does not exist.",
|
||||
"login successful": "Login successful."
|
||||
}
|
11
langs/en/apppasswords.json
Normal file
11
langs/en/apppasswords.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"App Passwords": "App Passwords",
|
||||
"app passwords explained": "Use app passwords instead of your actual password when logging into apps with your {site_name} login. App passwords are required in some places when you have 2-factor authentication enabled.",
|
||||
"app password setup instructions": "Use the username and password below to log in to {app_name}. You'll only be shown this password one time.",
|
||||
"App name": "App name",
|
||||
"Generate password": "Generate password",
|
||||
"Revoke password": "Revoke password",
|
||||
"You don't have any app passwords.": "You don't have any app passwords.",
|
||||
"Done": "Done",
|
||||
"App passwords are not allowed here.": "App passwords are not allowed here."
|
||||
}
|
13
langs/en/cards.json
Normal file
13
langs/en/cards.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"Create virtual notes and lists": "Create virtual notes and lists",
|
||||
"Punch in and check work schedule": "Punch in and check work schedule",
|
||||
"Manage physical items": "Manage physical items",
|
||||
"Create and publish e-newsletters": "Create and publish e-newsletters",
|
||||
"Manage users, permissions, and security": "Manage users, permissions, and security",
|
||||
"Checkout customers and manage online orders": "Checkout customers and manage online orders",
|
||||
"Build websites and manage contact form messages": "Build websites and manage contact form messages",
|
||||
"Track jobs and assigned tasks": "Track jobs and assigned tasks",
|
||||
"Change password, setup 2-factor, and add app passwords": "Change password, setup 2-factor, and add app passwords",
|
||||
"Change password, setup 2-factor, add app passwords, and change PIN": "Change password, setup 2-factor, add app passwords, and change PIN",
|
||||
"Connect mobile devices to {name} and get notifications": "Connect mobile devices to {name} and get notifications"
|
||||
}
|
7
langs/en/core.json
Normal file
7
langs/en/core.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"sign out": "Sign out",
|
||||
"404 error": "404 Error",
|
||||
"page not found": "Page not found.",
|
||||
"invalid parameters": "Invalid request parameters.",
|
||||
"login server error": "The login server returned an error: {arg}"
|
||||
}
|
7
langs/en/index.json
Normal file
7
langs/en/index.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"You have been logged out.": "You have been logged out.",
|
||||
"Log in again": "Log in again",
|
||||
"login server unavailable": "Login server unavailable. Try again later or contact technical support.",
|
||||
"no access permission": "You do not have permission to access this system.",
|
||||
"Logged in": "Logged in"
|
||||
}
|
4
langs/en/ldap.json
Normal file
4
langs/en/ldap.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"ldap server error": "The LDAP server returned an error: {arg}",
|
||||
"ldap error": "LDAP error: {error}"
|
||||
}
|
25
langs/en/login.json
Normal file
25
langs/en/login.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"Login to {app}": "Login to {app}",
|
||||
"Username not found.": "Username not found.",
|
||||
"Password for {user}": "Password for {user}",
|
||||
"Password incorrect.": "Password incorrect.",
|
||||
"Two-factor code": "Two-factor code",
|
||||
"Code incorrect.": "Code incorrect.",
|
||||
"Current password for {user}": "Current password for {user}",
|
||||
"New password": "New password",
|
||||
"New password (again)": "New password (again)",
|
||||
"Fill in all three boxes.": "Fill in all three boxes.",
|
||||
"sign in": "Sign In",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"continue": "Continue",
|
||||
"authcode": "Authentication code",
|
||||
"2fa prompt": "Enter the six-digit code from your mobile authenticator app.",
|
||||
"2fa incorrect": "Authentication code incorrect.",
|
||||
"login incorrect": "Login incorrect.",
|
||||
"account locked": "This account has been disabled. Contact technical support.",
|
||||
"password expired": "You must change your password before continuing.",
|
||||
"account terminated": "Account terminated. Access denied.",
|
||||
"account state error": "Your account state is not stable. Log out, restart your browser, and try again.",
|
||||
"Back": "Back"
|
||||
}
|
9
langs/en/notifications.json
Normal file
9
langs/en/notifications.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Notifications": "Notifications",
|
||||
"Notification deleted.": "Notification deleted.",
|
||||
"Mark as read": "Mark as read",
|
||||
"Delete": "Delete",
|
||||
"All caught up!": "All caught up!",
|
||||
"Notifications from server for user": "Notifications from {server} for {user}",
|
||||
"Sensitive content hidden": "Sensitive content hidden"
|
||||
}
|
13
langs/en/password.json
Normal file
13
langs/en/password.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"password on 500 list": "The given password is ranked number {arg} out of the 500 most common passwords. Try a different one.",
|
||||
"change password": "Change password",
|
||||
"current password incorrect": "The current password is incorrect. Try again.",
|
||||
"new password mismatch": "The new passwords did not match. Try again.",
|
||||
"weak password": "Password does not meet requirements.",
|
||||
"password updated": "Password updated successfully.",
|
||||
"current password": "Current password",
|
||||
"new password": "New password",
|
||||
"confirm password": "New password (again)",
|
||||
"password complexity insufficent": "The new password does not meet the minumum requirements defined by your system administrator.",
|
||||
"old and new passwords match": "Your current and new passwords are the same."
|
||||
}
|
9
langs/en/pin.json
Normal file
9
langs/en/pin.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"pin explanation": "Change or set a login PIN for the Station kiosk Quick Access. PIN codes must be between one and eight digits.",
|
||||
"change pin": "Change PIN",
|
||||
"new pin": "New PIN",
|
||||
"confirm pin": "New PIN (again)",
|
||||
"pin updated": "PIN updated.",
|
||||
"new pin mismatch": "The new PINs don't match each other.",
|
||||
"invalid pin format": "PIN codes must be numeric and between one and eight digits in length."
|
||||
}
|
7
langs/en/security.json
Normal file
7
langs/en/security.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"sign in again": "Please sign in again to continue.",
|
||||
"login failed try on web": "There is a problem with your account. Visit AccountHub via a web browser for more information.",
|
||||
"mobile login disabled": "Mobile login has been disabled by your system administrator. Contact technical support for more information.",
|
||||
"admin alert email subject": "Alert: User login notification",
|
||||
"admin alert email message": "You (or another administrator) requested to be notified when user \"{username}\" logged in, an event which happened just now.\r\n\r\nUsername: \t{username}\r\nApplication: \t{appname}\r\nDate\/Time: \t{datetime}\r\nIP address: \t{ipaddr}\r\n\r\nThese notifications can be disabled by editing the user in ManagePanel."
|
||||
}
|
15
langs/en/signup.json
Normal file
15
langs/en/signup.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"Create Account": "Create Account",
|
||||
"Account Created": "Account Created",
|
||||
"Choose a username.": "Choose a username.",
|
||||
"Choose a password.": "Choose a password.",
|
||||
"Enter your name.": "Enter your name.",
|
||||
"Username already taken, pick another.": "Username already taken, pick another.",
|
||||
"Your password must be at least {n} characters long.": "Your password must be at least {n} characters long.",
|
||||
"That email address doesn't look right.": "That email address doesn't look right.",
|
||||
"Please enter your username (4-100 characters, alphanumeric).": "Please enter your username (4-100 characters, alphanumeric).",
|
||||
"That password is one of the most popular and insecure ever, make a better one.": "That password is one of the most popular and insecure ever, make a better one.",
|
||||
"Account creation not allowed. Contact the site administrator for an account.": "Account creation not allowed. Contact the site administrator for an account.",
|
||||
"CAPTCHA answer incorrect.": "CAPTCHA answer incorrect.",
|
||||
"That email address is already in use.": "That email address is already in use."
|
||||
}
|
17
langs/en/sync.json
Normal file
17
langs/en/sync.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"sync mobile": "Sync Mobile App",
|
||||
"scan sync qrcode": "Scan this code with the mobile app or enter the code manually.",
|
||||
"sync explained": "Access your account and apps on the go. Use a sync code to securely connect your phone or tablet to {site_name} with the Netsyms mobile app.",
|
||||
"generate sync": "Create new sync code",
|
||||
"active sync codes": "Active codes",
|
||||
"no active codes": "No active codes.",
|
||||
"done adding sync code": "Done adding code",
|
||||
"manual setup": "Manual Setup:",
|
||||
"sync key": "Sync key:",
|
||||
"url": "URL:",
|
||||
"sync code name": "Device nickname",
|
||||
"notification feed explained": "You can receive notifications via a RSS or ATOM news reader by clicking one of the buttons or manually adding a URL. Click the Reset button if you think someone else might know your feed URL (you'll need to delete and re-add the feed on all your devices).",
|
||||
"Reset": "Reset",
|
||||
"Feed key reset.": "Feed key reset.",
|
||||
"Revoke key": "Revoke key"
|
||||
}
|
9
langs/en/titles.json
Normal file
9
langs/en/titles.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"account security": "Account security",
|
||||
"security options": "Security options",
|
||||
"account options": "Account options",
|
||||
"sync": "Sync settings",
|
||||
"settings": "Settings",
|
||||
"account": "Account",
|
||||
"Home": "Home"
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define("MESSAGES", [
|
||||
"old_password_mismatch" => [
|
||||
"string" => "current password incorrect",
|
||||
@ -25,6 +29,10 @@ define("MESSAGES", [
|
||||
"string" => "2fa enabled",
|
||||
"type" => "success"
|
||||
],
|
||||
"2fa_wrong_code" => [
|
||||
"string" => "2fa incorrect",
|
||||
"type" => "danger"
|
||||
],
|
||||
"invalid_parameters" => [
|
||||
"string" => "invalid parameters",
|
||||
"type" => "danger"
|
||||
@ -37,10 +45,6 @@ define("MESSAGES", [
|
||||
"string" => "account state error",
|
||||
"type" => "danger"
|
||||
],
|
||||
"ldap_error" => [
|
||||
"string" => "ldap server error",
|
||||
"type" => "danger"
|
||||
],
|
||||
"passwords_same" => [
|
||||
"string" => "old and new passwords match",
|
||||
"type" => "danger"
|
||||
@ -52,5 +56,25 @@ define("MESSAGES", [
|
||||
"generic_op_error" => [
|
||||
"string" => "generic op error",
|
||||
"type" => "danger"
|
||||
],
|
||||
"pin_updated" => [
|
||||
"string" => "pin updated",
|
||||
"type" => "success"
|
||||
],
|
||||
"new_pin_mismatch" => [
|
||||
"string" => "new pin mismatch",
|
||||
"type" => "danger"
|
||||
],
|
||||
"invalid_pin_format" => [
|
||||
"string" => "invalid pin format",
|
||||
"type" => "danger"
|
||||
],
|
||||
"notification_deleted" => [
|
||||
"string" => "Notification deleted.",
|
||||
"type" => "success"
|
||||
],
|
||||
"feed_key_reset" => [
|
||||
"string" => "Feed key reset.",
|
||||
"type" => "success"
|
||||
]
|
||||
]);
|
35
lib/Exceptions.lib.php
Normal file
35
lib/Exceptions.lib.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class IncorrectPasswordException extends Exception {
|
||||
public function __construct(string $message = "Incorrect password.", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class WeakPasswordException extends Exception {
|
||||
public function __construct(string $message = "Password is weak or compromised.", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordMatchException extends Exception {
|
||||
|
||||
public function __construct(string $message = "Old and new passwords are identical", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PasswordMismatchException extends Exception {
|
||||
|
||||
public function __construct(string $message = "Passwords do not match", int $code = 0, \Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
326
lib/FormBuilder.lib.php
Normal file
326
lib/FormBuilder.lib.php
Normal file
@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class FormBuilder {
|
||||
|
||||
private $items = [];
|
||||
private $hiddenitems = [];
|
||||
private $title = "";
|
||||
private $icon = "";
|
||||
private $buttons = [];
|
||||
private $action = "action.php";
|
||||
private $method = "POST";
|
||||
private $id = "editform";
|
||||
|
||||
/**
|
||||
* Create a form with autogenerated HTML.
|
||||
*
|
||||
* @param string $title Form title/heading
|
||||
* @param string $icon FontAwesone icon next to the title.
|
||||
* @param string $action URL to submit the form to.
|
||||
* @param string $method Form submission method (POST, GET, etc.)
|
||||
*/
|
||||
public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
|
||||
$this->title = $title;
|
||||
$this->icon = $icon;
|
||||
$this->action = $action;
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title of the form.
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle(string $title) {
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the icon for the form.
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
*/
|
||||
public function setIcon(string $icon) {
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL the form will submit to.
|
||||
* @param string $action
|
||||
*/
|
||||
public function setAction(string $action) {
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the form submission method (GET, POST, etc)
|
||||
* @param string $method
|
||||
*/
|
||||
public function setMethod(string $method = "POST") {
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the form ID.
|
||||
* @param string $id
|
||||
*/
|
||||
public function setID(string $id = "editform") {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input to the form.
|
||||
*
|
||||
* @param string $name Element name
|
||||
* @param string $value Element value
|
||||
* @param string $type Input type (text, number, date, select, tel...)
|
||||
* @param bool $required If the element is required for form submission.
|
||||
* @param string $id Element ID
|
||||
* @param array $options Array of [value => text] pairs for a select element
|
||||
* @param string $label Text label to display near the input
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
* @param int $width Bootstrap column width for the input, out of 12.
|
||||
* @param int $minlength Minimum number of characters for the input.
|
||||
* @param int $maxlength Maximum number of characters for the input.
|
||||
* @param string $pattern Regex pattern for custom client-side validation.
|
||||
* @param string $error Message to show if the input doesn't validate.
|
||||
*/
|
||||
public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
|
||||
$item = [
|
||||
"name" => $name,
|
||||
"value" => $value,
|
||||
"type" => $type,
|
||||
"required" => $required,
|
||||
"label" => $label,
|
||||
"icon" => $icon,
|
||||
"width" => $width,
|
||||
"minlength" => $minlength,
|
||||
"maxlength" => $maxlength
|
||||
];
|
||||
if (!empty($id)) {
|
||||
$item["id"] = $id;
|
||||
}
|
||||
if (!empty($options) && $type == "select") {
|
||||
$item["options"] = $options;
|
||||
}
|
||||
if (!empty($pattern)) {
|
||||
$item["pattern"] = $pattern;
|
||||
}
|
||||
if (!empty($error)) {
|
||||
$item["error"] = $error;
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a text input.
|
||||
*
|
||||
* @param string $name Element name
|
||||
* @param string $value Element value
|
||||
* @param bool $required If the element is required for form submission.
|
||||
* @param string $id Element ID
|
||||
* @param string $label Text label to display near the input
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
* @param int $width Bootstrap column width for the input, out of 12.
|
||||
* @param int $minlength Minimum number of characters for the input.
|
||||
* @param int $maxlength Maximum number of characters for the input.
|
||||
* @param string $pattern Regex pattern for custom client-side validation.
|
||||
* @param string $error Message to show if the input doesn't validate.
|
||||
*/
|
||||
public function addTextInput(string $name, string $value = "", bool $required = true, string $id = "", string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
|
||||
$this->addInput($name, $value, "text", $required, $id, null, $label, $icon, $width, $minlength, $maxlength, $pattern, $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a select dropdown.
|
||||
*
|
||||
* @param string $name Element name
|
||||
* @param string $value Element value
|
||||
* @param bool $required If the element is required for form submission.
|
||||
* @param string $id Element ID
|
||||
* @param array $options Array of [value => text] pairs for a select element
|
||||
* @param string $label Text label to display near the input
|
||||
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
|
||||
* @param int $width Bootstrap column width for the input, out of 12.
|
||||
*/
|
||||
public function addSelect(string $name, string $value = "", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4) {
|
||||
$this->addInput($name, $value, "select", $required, $id, $options, $label, $icon, $width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a button to the form.
|
||||
*
|
||||
* @param string $text Text string to show on the button.
|
||||
* @param string $icon FontAwesome icon to show next to the text.
|
||||
* @param string $href If not null, the button will actually be a hyperlink.
|
||||
* @param string $type Usually "button" or "submit". Ignored if $href is set.
|
||||
* @param string $id The element ID.
|
||||
* @param string $name The element name for the button.
|
||||
* @param string $value The form value for the button. Ignored if $name is null.
|
||||
* @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
|
||||
*/
|
||||
public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") {
|
||||
$button = [
|
||||
"text" => $text,
|
||||
"icon" => $icon,
|
||||
"class" => $class,
|
||||
"type" => $type,
|
||||
"id" => $id,
|
||||
"href" => $href,
|
||||
"name" => $name,
|
||||
"value" => $value
|
||||
];
|
||||
$this->buttons[] = $button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a hidden input.
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*/
|
||||
public function addHiddenInput(string $name, string $value) {
|
||||
$this->hiddenitems[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the form HTML.
|
||||
* @param bool $echo If false, returns HTML string instead of outputting it.
|
||||
*/
|
||||
public function generate(bool $echo = true) {
|
||||
$html = <<<HTMLTOP
|
||||
<form action="$this->action" method="$this->method" id="$this->id">
|
||||
<div class="card">
|
||||
<h3 class="card-header d-flex">
|
||||
<div>
|
||||
<i class="$this->icon"></i> $this->title
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
HTMLTOP;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$required = $item["required"] ? "required" : "";
|
||||
$id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
|
||||
$pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
|
||||
if (empty($item['type'])) {
|
||||
$item['type'] = "text";
|
||||
}
|
||||
$itemhtml = "";
|
||||
$itemlabel = "";
|
||||
|
||||
if ($item['type'] == "textarea") {
|
||||
$itemlabel = "<label class=\"mb-0\"><i class=\"$item[icon]\"></i> $item[label]:</label>";
|
||||
} else if ($item['type'] != "checkbox") {
|
||||
$itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
|
||||
}
|
||||
$strippedlabel = strip_tags($item['label']);
|
||||
$itemhtml .= <<<ITEMTOP
|
||||
\n\n <div class="col-12 col-md-$item[width]">
|
||||
<div class="form-group mb-3">
|
||||
$itemlabel
|
||||
ITEMTOP;
|
||||
$inputgrouptop = <<<INPUTG
|
||||
\n <div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="$item[icon]"></i></span>
|
||||
</div>
|
||||
INPUTG;
|
||||
switch ($item['type']) {
|
||||
case "select":
|
||||
$itemhtml .= $inputgrouptop;
|
||||
$itemhtml .= <<<SELECT
|
||||
\n <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
|
||||
SELECT;
|
||||
foreach ($item['options'] as $value => $label) {
|
||||
$selected = "";
|
||||
if (!empty($item['value']) && $value == $item['value']) {
|
||||
$selected = " selected";
|
||||
}
|
||||
$itemhtml .= "\n <option value=\"$value\"$selected>$label</option>";
|
||||
}
|
||||
$itemhtml .= "\n </select>";
|
||||
break;
|
||||
case "checkbox":
|
||||
$itemhtml .= $inputgrouptop;
|
||||
$itemhtml .= <<<CHECKBOX
|
||||
\n <div class="form-group form-check">
|
||||
<input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
|
||||
<label class="form-check-label">$item[label]</label>
|
||||
</div>
|
||||
CHECKBOX;
|
||||
break;
|
||||
case "textarea":
|
||||
$val = htmlentities($item['value']);
|
||||
$itemhtml .= <<<TEXTAREA
|
||||
\n <textarea class="form-control" id="info" name="$item[name]" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $required>$val</textarea>
|
||||
TEXTAREA;
|
||||
break;
|
||||
default:
|
||||
$itemhtml .= $inputgrouptop;
|
||||
$itemhtml .= <<<INPUT
|
||||
\n <input type="$item[type]" name="$item[name]" $id class="form-control" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $pattern value="$item[value]" $required />
|
||||
INPUT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($item["error"])) {
|
||||
$itemhtml .= <<<ERROR
|
||||
\n <div class="invalid-feedback">
|
||||
$item[error]
|
||||
</div>
|
||||
ERROR;
|
||||
}
|
||||
if ($item["type"] != "textarea") {
|
||||
$itemhtml .= "\n </div>";
|
||||
}
|
||||
$itemhtml .= <<<ITEMBOTTOM
|
||||
\n </div>
|
||||
</div>\n
|
||||
ITEMBOTTOM;
|
||||
$html .= $itemhtml;
|
||||
}
|
||||
|
||||
$html .= <<<HTMLBOTTOM
|
||||
|
||||
</div>
|
||||
</div>
|
||||
HTMLBOTTOM;
|
||||
|
||||
if (!empty($this->buttons)) {
|
||||
$html .= "\n <div class=\"card-footer d-flex\">";
|
||||
foreach ($this->buttons as $btn) {
|
||||
$btnhtml = "";
|
||||
$inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
|
||||
$id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
|
||||
if (!empty($btn['href'])) {
|
||||
$btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
|
||||
} else {
|
||||
$name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
|
||||
$value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
|
||||
$btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
|
||||
}
|
||||
$html .= "\n $btnhtml";
|
||||
}
|
||||
$html .= "\n </div>";
|
||||
}
|
||||
|
||||
$html .= "\n </div>";
|
||||
foreach ($this->hiddenitems as $name => $value) {
|
||||
$value = htmlentities($value);
|
||||
$html .= "\n <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
|
||||
}
|
||||
$html .= "\n</form>\n";
|
||||
|
||||
if ($echo) {
|
||||
echo $html;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
135
lib/IPUtils.lib.php
Normal file
135
lib/IPUtils.lib.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
class IPUtils {
|
||||
|
||||
/**
|
||||
* Check if a given ipv4 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
|
||||
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
|
||||
* @return boolean true if the ip is in this range / false if not.
|
||||
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
|
||||
*/
|
||||
public static function ip4_in_cidr($ip, $cidr) {
|
||||
if (strpos($cidr, '/') == false) {
|
||||
$cidr .= '/32';
|
||||
}
|
||||
// $range is in IP/CIDR format eg 127.0.0.1/24
|
||||
list( $cidr, $netmask ) = explode('/', $cidr, 2);
|
||||
$range_decimal = ip2long($cidr);
|
||||
$ip_decimal = ip2long($ip);
|
||||
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
|
||||
$netmask_decimal = ~ $wildcard_decimal;
|
||||
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given ipv6 address is in a given cidr
|
||||
* @param string $ip IP to check in IPV6 format
|
||||
* @param string $cidr CIDR netmask
|
||||
* @return boolean true if the IP is in this range, false otherwise.
|
||||
* @author MW. <https://stackoverflow.com/a/7952169>
|
||||
*/
|
||||
public static function ip6_in_cidr($ip, $cidr) {
|
||||
$address = inet_pton($ip);
|
||||
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
|
||||
$subnetMask = explode("/", $cidr)[1];
|
||||
|
||||
$addr = str_repeat("f", $subnetMask / 4);
|
||||
switch ($subnetMask % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
$addr .= "8";
|
||||
break;
|
||||
case 2:
|
||||
$addr .= "c";
|
||||
break;
|
||||
case 3:
|
||||
$addr .= "e";
|
||||
break;
|
||||
}
|
||||
$addr = str_pad($addr, 32, '0');
|
||||
$addr = pack("H*", $addr);
|
||||
|
||||
$binMask = $addr;
|
||||
return ($address & $binMask) == $subnetAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the REMOTE_ADDR is on Cloudflare's network.
|
||||
* @return boolean true if it is, otherwise false
|
||||
*/
|
||||
public static function validateCloudflare() {
|
||||
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
// Using IPv6
|
||||
$cloudflare_ips_v6 = [
|
||||
"2400:cb00::/32",
|
||||
"2405:8100::/32",
|
||||
"2405:b500::/32",
|
||||
"2606:4700::/32",
|
||||
"2803:f800::/32",
|
||||
"2c0f:f248::/32",
|
||||
"2a06:98c0::/29"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v6 as $cidr) {
|
||||
if ($this::ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Using IPv4
|
||||
$cloudflare_ips_v4 = [
|
||||
"103.21.244.0/22",
|
||||
"103.22.200.0/22",
|
||||
"103.31.4.0/22",
|
||||
"104.16.0.0/12",
|
||||
"108.162.192.0/18",
|
||||
"131.0.72.0/22",
|
||||
"141.101.64.0/18",
|
||||
"162.158.0.0/15",
|
||||
"172.64.0.0/13",
|
||||
"173.245.48.0/20",
|
||||
"188.114.96.0/20",
|
||||
"190.93.240.0/20",
|
||||
"197.234.240.0/22",
|
||||
"198.41.128.0/17"
|
||||
];
|
||||
$valid = false;
|
||||
foreach ($cloudflare_ips_v4 as $cidr) {
|
||||
if ($this::ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a good guess at the client's real IP address.
|
||||
*
|
||||
* @return string Client IP or `0.0.0.0` if we can't find anything
|
||||
*/
|
||||
public static function getClientIP() {
|
||||
// If CloudFlare is in the mix, we should use it.
|
||||
// Check if the request is actually from CloudFlare before trusting it.
|
||||
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
if ($this::validateCloudflare()) {
|
||||
return $_SERVER["HTTP_CF_CONNECTING_IP"];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER["REMOTE_ADDR"])) {
|
||||
return $_SERVER["REMOTE_ADDR"];
|
||||
}
|
||||
|
||||
return "0.0.0.0"; // This will not happen unless we aren't a web server
|
||||
}
|
||||
|
||||
}
|
72
lib/Log.lib.php
Normal file
72
lib/Log.lib.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Log {
|
||||
|
||||
/**
|
||||
*
|
||||
* @global $database
|
||||
* @param int/LogType $type Either an integer (as defined by the constants in class LogType) or a LogType object.
|
||||
* @param int/User $user Either a UID number or a User object.
|
||||
* @param string $data Extra data to include in the log, in addition to the timestamp, log type, user, and IP address.
|
||||
*/
|
||||
public static function insert($type, $user, string $data = "") {
|
||||
global $database;
|
||||
// find IP address
|
||||
$ip = IPUtils::getClientIP();
|
||||
if (gettype($type) == "object" && is_a($type, "LogType")) {
|
||||
$type = $type->getType();
|
||||
}
|
||||
|
||||
if (is_a($user, "User")) {
|
||||
$uid = $user->getUID();
|
||||
} else if (gettype($user) == "integer") {
|
||||
$uid = $user;
|
||||
} else {
|
||||
$uid = null;
|
||||
}
|
||||
|
||||
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LogType {
|
||||
|
||||
const LOGIN_OK = 1;
|
||||
const LOGIN_FAILED = 2;
|
||||
const PASSWORD_CHANGED = 3;
|
||||
const API_LOGIN_OK = 4;
|
||||
const API_LOGIN_FAILED = 5;
|
||||
const BAD_2FA = 6;
|
||||
const API_BAD_2FA = 7;
|
||||
const BAD_CAPTCHA = 8;
|
||||
const ADDED_2FA = 9;
|
||||
const REMOVED_2FA = 10;
|
||||
const LOGOUT = 11;
|
||||
const API_AUTH_OK = 12;
|
||||
const API_AUTH_FAILED = 13;
|
||||
const API_BAD_KEY = 14;
|
||||
const LOG_CLEARED = 15;
|
||||
const USER_REMOVED = 16;
|
||||
const USER_ADDED = 17;
|
||||
const USER_EDITED = 18;
|
||||
const MOBILE_LOGIN_OK = 19;
|
||||
const MOBILE_LOGIN_FAILED = 20;
|
||||
const MOBILE_BAD_KEY = 21;
|
||||
|
||||
private $type;
|
||||
|
||||
function __construct(int $type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getType(): int {
|
||||
return $type;
|
||||
}
|
||||
}
|
71
lib/Login.lib.php
Normal file
71
lib/Login.lib.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Login {
|
||||
|
||||
const BAD_USERPASS = 1;
|
||||
const BAD_2FA = 2;
|
||||
const ACCOUNT_DISABLED = 3;
|
||||
const LOGIN_OK = 4;
|
||||
|
||||
public static function auth(string $username, string $password, string $twofa = ""): int {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
|
||||
$user = User::byUsername($username);
|
||||
|
||||
if (!$user->exists()) {
|
||||
return Login::BAD_USERPASS;
|
||||
}
|
||||
if (!$user->checkPassword($password)) {
|
||||
return Login::BAD_USERPASS;
|
||||
}
|
||||
|
||||
if ($user->has2fa()) {
|
||||
if (!$user->check2fa($twofa)) {
|
||||
return Login::BAD_2FA;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::TERMINATED:
|
||||
return Login::BAD_USERPASS;
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
return Login::ACCOUNT_DISABLED;
|
||||
case AccountStatus::NORMAL:
|
||||
default:
|
||||
return Login::LOGIN_OK;
|
||||
}
|
||||
|
||||
return Login::LOGIN_OK;
|
||||
}
|
||||
|
||||
public static function verifyCaptcha(string $session, string $answer, string $url): bool {
|
||||
$data = [
|
||||
'session_id' => $session,
|
||||
'answer_id' => $answer,
|
||||
'action' => "verify"
|
||||
];
|
||||
$options = [
|
||||
'http' => [
|
||||
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
|
||||
'method' => 'POST',
|
||||
'content' => http_build_query($data)
|
||||
]
|
||||
];
|
||||
$context = stream_context_create($options);
|
||||
$result = file_get_contents($url, false, $context);
|
||||
$resp = json_decode($result, TRUE);
|
||||
if (!$resp['result']) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
lib/LoginKey.lib.php
Normal file
33
lib/LoginKey.lib.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class LoginKey {
|
||||
|
||||
public static function generate(string $appname, $appicon = null): string {
|
||||
global $database;
|
||||
do {
|
||||
$code = base64_encode(random_bytes(32));
|
||||
} while ($database->has('userloginkeys', ['key' => $code]));
|
||||
|
||||
$database->insert('userloginkeys', ['key' => $code, 'expires' => date("Y-m-d H:i:s", time() + 600), 'appname' => $appname, 'appicon' => $appicon]);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
public static function getuid(string $code): int {
|
||||
global $database;
|
||||
if (!$database->has('userloginkeys', ["AND" => ['key' => $code, 'uid[!]' => null]])) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$uid = $database->get('userloginkeys', 'uid', ['key' => $code]);
|
||||
|
||||
return $uid;
|
||||
}
|
||||
|
||||
}
|
99
lib/Notifications.lib.php
Normal file
99
lib/Notifications.lib.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Notifications {
|
||||
|
||||
/**
|
||||
* Add a new notification.
|
||||
* @global $database
|
||||
* @param User $user
|
||||
* @param string $title
|
||||
* @param string $content
|
||||
* @param string $timestamp If left empty, the current date and time will be used.
|
||||
* @param string $url
|
||||
* @param bool $sensitive If true, the notification is marked as containing sensitive content, and the $content might be hidden on lockscreens and other non-secure places.
|
||||
* @return int The newly-created notification ID.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if (empty($title) || empty($content)) {
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
|
||||
$timestamp = date("Y-m-d H:i:s");
|
||||
if (!empty($timestamp)) {
|
||||
$timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
|
||||
}
|
||||
|
||||
$database->insert('notifications', ['uid' => $user->getUID(), 'timestamp' => $timestamp, 'title' => $title, 'content' => $content, 'url' => $url, 'seen' => 0, 'sensitive' => $sensitive]);
|
||||
return $database->id() * 1;
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all notifications for a user.
|
||||
* @global $database
|
||||
* @param User $user
|
||||
* @param bool $all If false, only returns unseen notifications.
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function get(User $user, bool $all = true) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if ($all) {
|
||||
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ['uid' => $user->getUID(), 'ORDER' => ['seen', 'timestamp' => 'DESC']]);
|
||||
} else {
|
||||
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ["AND" => ['uid' => $user->getUID(), 'seen' => 0], 'ORDER' => ['timestamp' => 'DESC']]);
|
||||
}
|
||||
for ($i = 0; $i < count($notifications); $i++) {
|
||||
$notifications[$i]['id'] = $notifications[$i]['id'] * 1;
|
||||
$notifications[$i]['seen'] = ($notifications[$i]['seen'] == "1" ? true : false);
|
||||
$notifications[$i]['sensitive'] = ($notifications[$i]['sensitive'] == "1" ? true : false);
|
||||
}
|
||||
return $notifications;
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the notification identified by $id as read.
|
||||
* @global $database
|
||||
* @global $Strings
|
||||
* @param User $user
|
||||
* @param int $id
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function read(User $user, int $id) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if ($database->has('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]])) {
|
||||
$database->update('notifications', ['seen' => 1], ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]]);
|
||||
return true;
|
||||
}
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
public static function delete(User $user, int $id) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
if ($database->has('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]])) {
|
||||
$database->delete('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]]);
|
||||
return true;
|
||||
}
|
||||
throw new Exception($Strings->get("invalid parameters", false));
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
}
|
31
lib/RandomString.lib.php
Normal file
31
lib/RandomString.lib.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class RandomString {
|
||||
|
||||
/**
|
||||
* Generate a random string, using a cryptographically secure
|
||||
* pseudorandom number generator (random_int)
|
||||
*
|
||||
* From https://stackoverflow.com/a/31107425
|
||||
*
|
||||
* @param int $length How many characters do we want?
|
||||
* @param string $keyspace A string of all possible characters
|
||||
* to select from
|
||||
* @return string
|
||||
*/
|
||||
public static function generate(int $length, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string {
|
||||
$pieces = [];
|
||||
$max = mb_strlen($keyspace, '8bit') - 1;
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$pieces [] = $keyspace[random_int(0, $max)];
|
||||
}
|
||||
return implode('', $pieces);
|
||||
}
|
||||
|
||||
}
|
19
lib/Session.lib.php
Normal file
19
lib/Session.lib.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
class Session {
|
||||
|
||||
public static function start(User $user) {
|
||||
$_SESSION['username'] = $user->getUsername();
|
||||
$_SESSION['uid'] = $user->getUID();
|
||||
$_SESSION['email'] = $user->getEmail();
|
||||
$_SESSION['realname'] = $user->getName();
|
||||
$_SESSION['loggedin'] = true;
|
||||
}
|
||||
|
||||
}
|
122
lib/Strings.lib.php
Normal file
122
lib/Strings.lib.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides translated language strings.
|
||||
*/
|
||||
class Strings {
|
||||
|
||||
private $language = "en";
|
||||
private $strings = [];
|
||||
|
||||
public function __construct($language = "en") {
|
||||
if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
|
||||
throw new Exception("Invalid language code $language");
|
||||
}
|
||||
|
||||
$this->load("en");
|
||||
|
||||
if ($language == "en") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists(__DIR__ . "/../langs/$language/")) {
|
||||
$this->language = $language;
|
||||
$this->load($language);
|
||||
} else {
|
||||
trigger_error("Language $language could not be found.", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all JSON files for the specified language.
|
||||
* @param string $language
|
||||
*/
|
||||
private function load(string $language) {
|
||||
$files = glob(__DIR__ . "/../langs/$language/*.json");
|
||||
foreach ($files as $file) {
|
||||
$strings = json_decode(file_get_contents($file), true);
|
||||
foreach ($strings as $key => $val) {
|
||||
if (array_key_exists($key, $this->strings)) {
|
||||
trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
|
||||
}
|
||||
$this->strings[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add language strings dynamically.
|
||||
* @param array $strings ["key" => "value", ...]
|
||||
*/
|
||||
public function addStrings(array $strings) {
|
||||
foreach ($strings as $key => $val) {
|
||||
$this->strings[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I18N string getter. If the key isn't found, it outputs the key itself.
|
||||
* @param string $key
|
||||
* @param bool $echo True to echo the result, false to return it. Default is true.
|
||||
* @return string
|
||||
*/
|
||||
public function get(string $key, bool $echo = true): string {
|
||||
$str = $key;
|
||||
if (array_key_exists($key, $this->strings)) {
|
||||
$str = $this->strings[$key];
|
||||
} else {
|
||||
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($echo) {
|
||||
echo $str;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
|
||||
* @param string $key
|
||||
* @param array $replace key-value array of replacements.
|
||||
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
|
||||
* result will be "hello 123".
|
||||
* @param bool $echo True to echo the result, false to return it. Default is true.
|
||||
* @return string
|
||||
*/
|
||||
public function build(string $key, array $replace, bool $echo = true): string {
|
||||
$str = $key;
|
||||
if (array_key_exists($key, $this->strings)) {
|
||||
$str = $this->strings[$key];
|
||||
} else {
|
||||
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
|
||||
}
|
||||
|
||||
foreach ($replace as $find => $repl) {
|
||||
$str = str_replace("{" . $find . "}", $repl, $str);
|
||||
}
|
||||
|
||||
if ($echo) {
|
||||
echo $str;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a JSON key:value string for the supplied array of keys.
|
||||
* @param array $keys ["key1", "key2", ...]
|
||||
*/
|
||||
public function getJSON(array $keys): string {
|
||||
$strings = [];
|
||||
foreach ($keys as $k) {
|
||||
$strings[$k] = $this->get($k, false);
|
||||
}
|
||||
return json_encode($strings);
|
||||
}
|
||||
|
||||
}
|
329
lib/User.lib.php
Normal file
329
lib/User.lib.php
Normal file
@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use Base32\Base32;
|
||||
use OTPHP\TOTP;
|
||||
|
||||
class User {
|
||||
|
||||
private $uid = null;
|
||||
private $username;
|
||||
private $passhash;
|
||||
private $email;
|
||||
private $realname;
|
||||
private $authsecret;
|
||||
private $has2fa = false;
|
||||
private $exists = false;
|
||||
private $apppasswords = [];
|
||||
|
||||
public function __construct(int $uid, string $username = "") {
|
||||
global $database;
|
||||
if ($database->has('accounts', ['AND' => ['uid' => $uid, 'deleted' => false]])) {
|
||||
$this->uid = $uid;
|
||||
$user = $database->get('accounts', ['username', 'password', 'email', 'realname', 'authsecret'], ['uid' => $uid]);
|
||||
$this->username = $user['username'];
|
||||
$this->passhash = $user['password'];
|
||||
$this->email = $user['email'];
|
||||
$this->realname = $user['realname'];
|
||||
$this->authsecret = $user['authsecret'];
|
||||
$this->has2fa = !empty($user['authsecret']);
|
||||
$this->exists = true;
|
||||
$this->apppasswords = $database->select('apppasswords', 'hash', ['uid' => $this->uid]);
|
||||
} else {
|
||||
$this->uid = $uid;
|
||||
$this->username = $username;
|
||||
}
|
||||
}
|
||||
|
||||
public static function byUsername(string $username): User {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
if ($database->has('accounts', ['AND' => ['username' => $username, 'deleted' => false]])) {
|
||||
$uid = $database->get('accounts', 'uid', ['username' => $username]);
|
||||
return new self($uid * 1);
|
||||
}
|
||||
return new self(-1, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user to the system. /!\ Assumes input is OK /!\
|
||||
* @param string $username Username, saved in lowercase.
|
||||
* @param string $password Password, will be hashed before saving.
|
||||
* @param string $realname User's real legal name
|
||||
* @param string $email User's email address.
|
||||
* @param string $phone1 Phone number #1
|
||||
* @param string $phone2 Phone number #2
|
||||
* @param int $type Account type
|
||||
* @return int The new user's ID number in the database.
|
||||
*/
|
||||
public static function add(string $username, string $password, string $realname, string $email = null, string $phone1 = "", string $phone2 = "", int $type = 1): int {
|
||||
global $database;
|
||||
$database->insert('accounts', [
|
||||
'username' => strtolower($username),
|
||||
'password' => (is_null($password) ? null : password_hash($password, PASSWORD_BCRYPT)),
|
||||
'realname' => $realname,
|
||||
'email' => $email,
|
||||
'phone1' => $phone1,
|
||||
'phone2' => $phone2,
|
||||
'acctstatus' => 1,
|
||||
'accttype' => $type
|
||||
]);
|
||||
return $database->id();
|
||||
}
|
||||
|
||||
public function exists(): bool {
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
public function has2fa(): bool {
|
||||
return $this->has2fa;
|
||||
}
|
||||
|
||||
function getUsername() {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
function getUID() {
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
function getEmail() {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return $this->realname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given plaintext password against the stored hash.
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
function checkPassword(string $password): bool {
|
||||
return password_verify($password, $this->passhash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given password against the user's app passwords.
|
||||
* @param string $apppassword
|
||||
* @return bool
|
||||
*/
|
||||
function checkAppPassword(string $apppassword): bool {
|
||||
foreach ($this->apppasswords as $hash) {
|
||||
if (password_verify($apppassword, $hash)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the user's password.
|
||||
* @global $database $database
|
||||
* @param string $old The current password
|
||||
* @param string $new The new password
|
||||
* @param string $new2 New password again
|
||||
* @throws PasswordMatchException
|
||||
* @throws PasswordMismatchException
|
||||
* @throws IncorrectPasswordException
|
||||
* @throws WeakPasswordException
|
||||
*/
|
||||
function changePassword(string $old, string $new, string $new2) {
|
||||
global $database, $SETTINGS;
|
||||
if ($old == $new) {
|
||||
throw new PasswordMatchException();
|
||||
}
|
||||
if ($new != $new2) {
|
||||
throw new PasswordMismatchException();
|
||||
}
|
||||
|
||||
if (!$this->checkPassword($old)) {
|
||||
throw new IncorrectPasswordException();
|
||||
}
|
||||
|
||||
require_once __DIR__ . "/worst_passwords.php";
|
||||
|
||||
$passrank = checkWorst500List($new);
|
||||
if ($passrank !== FALSE) {
|
||||
throw new WeakPasswordException();
|
||||
}
|
||||
if (strlen($new) < $SETTINGS['min_password_length']) {
|
||||
throw new WeakPasswordException();
|
||||
}
|
||||
|
||||
$database->update('accounts', ['password' => password_hash($new, PASSWORD_DEFAULT), 'acctstatus' => 1], ['uid' => $this->uid]);
|
||||
Log::insert(LogType::PASSWORD_CHANGED, $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function check2fa(string $code): bool {
|
||||
if (!$this->has2fa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$totp = new TOTP(null, $this->authsecret);
|
||||
$time = time();
|
||||
if ($totp->verify($code, $time)) {
|
||||
return true;
|
||||
}
|
||||
if ($totp->verify($code, $time - 30)) {
|
||||
return true;
|
||||
}
|
||||
if ($totp->verify($code, $time + 30)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a TOTP secret for the given user.
|
||||
* @return string OTP provisioning URI (for generating a QR code)
|
||||
*/
|
||||
function generate2fa(): string {
|
||||
global $SETTINGS;
|
||||
$secret = random_bytes(20);
|
||||
$encoded_secret = Base32::encode($secret);
|
||||
$totp = new TOTP((empty($this->email) ? $this->realname : $this->email), $encoded_secret);
|
||||
$totp->setIssuer($SETTINGS['system_name']);
|
||||
return $totp->getProvisioningUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a TOTP secret for the user.
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $secret
|
||||
*/
|
||||
function save2fa(string $secret) {
|
||||
global $database;
|
||||
$database->update('accounts', ['authsecret' => $secret], ['username' => $this->username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given username has the given permission (or admin access)
|
||||
* @global $database $database
|
||||
* @param string $code
|
||||
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
|
||||
*/
|
||||
function hasPermission(string $code): bool {
|
||||
global $database;
|
||||
return $database->has('assigned_permissions', [
|
||||
'[>]permissions' => [
|
||||
'permid' => 'permid'
|
||||
]
|
||||
], ['AND' => ['OR' => ['permcode #code' => $code, 'permcode #admin' => 'ADMIN'], 'uid' => $this->uid]]) === TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status.
|
||||
* @return \AccountStatus
|
||||
*/
|
||||
function getStatus(): AccountStatus {
|
||||
global $database;
|
||||
$statuscode = $database->get('accounts', 'acctstatus', ['uid' => $this->uid]);
|
||||
return new AccountStatus($statuscode);
|
||||
}
|
||||
|
||||
function sendAlertEmail(string $appname = null) {
|
||||
global $SETTINGS, $Strings;
|
||||
if (is_null($appname)) {
|
||||
$appname = $SETTINGS['site_title'];
|
||||
}
|
||||
if (empty($SETTINGS["email"]["admin_email"]) || filter_var($SETTINGS["email"]["admin_email"], FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
return "invalid_to_email";
|
||||
}
|
||||
if (empty($SETTINGS["email"]["from"]) || filter_var($SETTINGS["email"]["from"], FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
return "invalid_from_email";
|
||||
}
|
||||
|
||||
$mail = new PHPMailer;
|
||||
|
||||
if ($SETTINGS['debug']) {
|
||||
$mail->SMTPDebug = 2;
|
||||
}
|
||||
|
||||
if ($SETTINGS['email']['use_smtp']) {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $SETTINGS['email']['host'];
|
||||
$mail->SMTPAuth = $SETTINGS['email']['auth'];
|
||||
$mail->Username = $SETTINGS['email']['user'];
|
||||
$mail->Password = $SETTINGS['email']['password'];
|
||||
$mail->SMTPSecure = $SETTINGS['email']['secure'];
|
||||
$mail->Port = $SETTINGS['email']['port'];
|
||||
if ($SETTINGS['email']['allow_invalid_certificate']) {
|
||||
$mail->SMTPOptions = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$mail->setFrom($SETTINGS["email"]["from"], 'Account Alerts');
|
||||
$mail->addAddress($SETTINGS["email"]["admin_email"], "System Admin");
|
||||
$mail->isHTML(false);
|
||||
$mail->Subject = $Strings->get("admin alert email subject", false);
|
||||
$mail->Body = $Strings->build("admin alert email message", ["username" => $this->username, "datetime" => date("Y-m-d H:i:s"), "ipaddr" => IPUtils::getClientIP(), "appname" => $appname], false);
|
||||
|
||||
if (!$mail->send()) {
|
||||
return $mail->ErrorInfo;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccountStatus {
|
||||
|
||||
const NORMAL = 1;
|
||||
const LOCKED_OR_DISABLED = 2;
|
||||
const CHANGE_PASSWORD = 3;
|
||||
const TERMINATED = 4;
|
||||
const ALERT_ON_ACCESS = 5;
|
||||
|
||||
private $status;
|
||||
|
||||
public function __construct(int $status) {
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status/state as an integer.
|
||||
* @return int
|
||||
*/
|
||||
public function get(): int {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status/state as a string representation.
|
||||
* @return string
|
||||
*/
|
||||
public function getString(): string {
|
||||
switch ($this->status) {
|
||||
case self::NORMAL:
|
||||
return "NORMAL";
|
||||
case self::LOCKED_OR_DISABLED:
|
||||
return "LOCKED_OR_DISABLED";
|
||||
case self::CHANGE_PASSWORD:
|
||||
return "CHANGE_PASSWORD";
|
||||
case self::TERMINATED:
|
||||
return "TERMINATED";
|
||||
case self::ALERT_ON_ACCESS:
|
||||
return "ALERT_ON_ACCESS";
|
||||
default:
|
||||
return "OTHER_" . $this->status;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
510
lib/login.php
510
lib/login.php
@ -1,510 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Authentication and account functions
|
||||
*/
|
||||
use Base32\Base32;
|
||||
use OTPHP\TOTP;
|
||||
use LdapTools\LdapManager;
|
||||
use LdapTools\Object\LdapObjectType;
|
||||
|
||||
$ldap = new LdapManager($ldap_config);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Account handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Add a user to the system. /!\ Assumes input is OK /!\
|
||||
* @param string $username Username, saved in lowercase.
|
||||
* @param string $password Password, will be hashed before saving.
|
||||
* @param string $realname User's real legal name
|
||||
* @param string $email User's email address.
|
||||
* @param string $phone1 Phone number #1
|
||||
* @param string $phone2 Phone number #2
|
||||
* @param string $type Account type
|
||||
* @return int The new user's ID number in the database.
|
||||
*/
|
||||
function adduser($username, $password, $realname, $email = null, $phone1 = "", $phone2 = "", $type) {
|
||||
global $database;
|
||||
$database->insert('accounts', [
|
||||
'username' => strtolower($username),
|
||||
'password' => (is_null($password) ? null : encryptPassword($password)),
|
||||
'realname' => $realname,
|
||||
'email' => $email,
|
||||
'phone1' => $phone1,
|
||||
'phone2' => $phone2,
|
||||
'acctstatus' => 1,
|
||||
'accttype' => $type
|
||||
]);
|
||||
//var_dump($database->error());
|
||||
return $database->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the password for the current user.
|
||||
* @global $database $database
|
||||
* @global LdapManager $ldap
|
||||
* @param string $old The current password
|
||||
* @param string $new The new password
|
||||
* @param string $new2 New password again
|
||||
* @param [string] $error If the function returns false, this will have an array
|
||||
* with a message ID from `lang/messages.php` and (depending on the message) an
|
||||
* extra string for that message.
|
||||
* @return boolean true if the password is changed, else false
|
||||
*/
|
||||
function change_password($old, $new, $new2, &$error) {
|
||||
global $database, $ldap;
|
||||
// make sure the new password isn't the same as the current one
|
||||
if ($old == $new) {
|
||||
$error = ["passwords_same"];
|
||||
return false;
|
||||
}
|
||||
// Make sure the new passwords are the same
|
||||
if ($new != $new2) {
|
||||
$error = ["new_password_mismatch"];
|
||||
return false;
|
||||
}
|
||||
// check the current password
|
||||
$login_ok = authenticate_user($_SESSION['username'], $old, $errmsg, $errcode);
|
||||
// Allow login if the error is due to expired password
|
||||
if (!$login_ok && ($errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED || $errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE)) {
|
||||
$login_ok = true;
|
||||
}
|
||||
if ($login_ok) {
|
||||
// Check the new password and make sure it's not stupid
|
||||
require_once __DIR__ . "/worst_passwords.php";
|
||||
$passrank = checkWorst500List($new);
|
||||
if ($passrank !== FALSE) {
|
||||
$error = ["password_500", $passrank];
|
||||
return false;
|
||||
}
|
||||
if (strlen($new) < MIN_PASSWORD_LENGTH) {
|
||||
$error = ["weak_password"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Figure out how to change the password, then do it
|
||||
$acctloc = account_location($_SESSION['username']);
|
||||
if ($acctloc == "LOCAL") {
|
||||
$database->update('accounts', ['password' => encryptPassword($VARS['newpass'])], ['uid' => $_SESSION['uid']]);
|
||||
$_SESSION['password'] = $VARS['newpass'];
|
||||
insertAuthLog(3, $_SESSION['uid']);
|
||||
return true;
|
||||
} else if ($acctloc == "LDAP") {
|
||||
try {
|
||||
$repository = $ldap->getRepository(LdapObjectType::USER);
|
||||
$user = $repository->findOneByUsername($_SESSION['username']);
|
||||
$user->setPassword($VARS['newpass']);
|
||||
$user->setpasswordMustChange(false);
|
||||
$ldap->persist($user);
|
||||
insertAuthLog(3, $_SESSION['uid']);
|
||||
$_SESSION['password'] = $VARS['newpass'];
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
// Stupid password complexity BS error
|
||||
if (strpos($e->getMessage(), "DSID-031A11E5") !== FALSE) {
|
||||
$error = ["password_complexity"];
|
||||
return false;
|
||||
}
|
||||
$error = ["ldap_error", $e->getMessage()];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$error = ["account_state_error"];
|
||||
return false;
|
||||
}
|
||||
$error = ["old_password_mismatch"];
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where a user's account actually is.
|
||||
* @param string $username
|
||||
* @return string "LDAP", "LOCAL", "LDAP_ONLY", or "NONE".
|
||||
*/
|
||||
function account_location($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$user_exists = user_exists_local($username);
|
||||
if (!$user_exists && !LDAP_ENABLED) {
|
||||
return false;
|
||||
}
|
||||
if ($user_exists) {
|
||||
$userinfo = $database->select('accounts', ['password'], ['username' => $username])[0];
|
||||
// if password empty, it's an LDAP user
|
||||
if (is_empty($userinfo['password']) && LDAP_ENABLED) {
|
||||
return "LDAP";
|
||||
} else if (is_empty($userinfo['password']) && !LDAP_ENABLED) {
|
||||
return "NONE";
|
||||
} else {
|
||||
return "LOCAL";
|
||||
}
|
||||
} else {
|
||||
if (user_exists_ldap($username)) {
|
||||
return "LDAP_ONLY";
|
||||
} else {
|
||||
return "NONE";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given credentials against the database.
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean True if OK, else false
|
||||
*/
|
||||
function authenticate_user($username, $password, &$errormsg, &$errorcode) {
|
||||
global $database;
|
||||
global $ldap;
|
||||
$username = strtolower($username);
|
||||
if (is_empty($username) || is_empty($password)) {
|
||||
return false;
|
||||
}
|
||||
$loc = account_location($username, $password);
|
||||
switch ($loc) {
|
||||
case "LOCAL":
|
||||
$hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
|
||||
return (comparePassword($password, $hash));
|
||||
case "LDAP":
|
||||
return authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE;
|
||||
case "LDAP_ONLY":
|
||||
// Authenticate with LDAP and create database account
|
||||
try {
|
||||
if (authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE) {
|
||||
$user = $ldap->getRepository('user')->findOneByUsername($username);
|
||||
|
||||
adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
$errormsg = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function user_exists($username) {
|
||||
return account_location($username) !== "NONE";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a username exists in the local database.
|
||||
* @param String $username
|
||||
*/
|
||||
function user_exists_local($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
|
||||
* CHANGE_PASSWORD, ALERT_ON_ACCESS, or OTHER
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
function get_account_status($username, &$error) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$loc = account_location($username);
|
||||
if ($loc == "LOCAL") {
|
||||
$statuscode = $database->select('accounts', [
|
||||
'[>]acctstatus' => [
|
||||
'acctstatus' => 'statusid'
|
||||
]
|
||||
], [
|
||||
'accounts.acctstatus',
|
||||
'acctstatus.statuscode'
|
||||
], [
|
||||
'username' => $username,
|
||||
"LIMIT" => 1
|
||||
]
|
||||
)[0]['statuscode'];
|
||||
return $statuscode;
|
||||
} else if ($loc == "LDAP" || $loc == "LDAP_ONLY") {
|
||||
return get_account_status_ldap($username, $error);
|
||||
} else {
|
||||
// account isn't setup properly
|
||||
return "OTHER";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given username has the given permission (or admin access)
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $permcode
|
||||
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
|
||||
*/
|
||||
function account_has_permission($username, $permcode) {
|
||||
global $database;
|
||||
return $database->has('assigned_permissions', [
|
||||
'[>]accounts' => [
|
||||
'uid' => 'uid'
|
||||
],
|
||||
'[>]permissions' => [
|
||||
'permid' => 'permid'
|
||||
]
|
||||
], ['AND' => ['OR' => ['permcode #code' => $permcode, 'permcode #admin' => 'ADMIN'], 'username' => $username]]) === TRUE;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Login handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Setup $_SESSION values to log in a user
|
||||
* @param string $username
|
||||
*/
|
||||
function doLoginUser($username, $password) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
|
||||
$_SESSION['username'] = $username;
|
||||
$_SESSION['uid'] = $userinfo['uid'];
|
||||
$_SESSION['email'] = $userinfo['email'];
|
||||
$_SESSION['realname'] = $userinfo['realname'];
|
||||
$_SESSION['password'] = $password; // needed for accessing data in other apps
|
||||
$_SESSION['loggedin'] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an alert email to the system admin
|
||||
*
|
||||
* Used when an account with the status ALERT_ON_ACCESS logs in
|
||||
* @param String $username the account username
|
||||
*/
|
||||
function sendLoginAlertEmail($username) {
|
||||
// TODO: add email code
|
||||
}
|
||||
|
||||
function insertAuthLog($type, $uid = null, $data = "") {
|
||||
global $database;
|
||||
// find IP address
|
||||
$ip = "";
|
||||
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];
|
||||
} else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
|
||||
$ip = $_SERVER["HTTP_CLIENT_IP"];
|
||||
} else if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
|
||||
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
|
||||
} else if (isset($_SERVER["HTTP_X_FORWARDED"])) {
|
||||
$ip = $_SERVER["HTTP_X_FORWARDED"];
|
||||
} else if (isset($_SERVER["HTTP_FORWARDED_FOR"])) {
|
||||
$ip = $_SERVER["HTTP_FORWARDED_FOR"];
|
||||
} else if (isset($_SERVER["HTTP_FORWARDED"])) {
|
||||
$ip = $_SERVER["HTTP_FORWARDED"];
|
||||
} else if (isset($_SERVER["REMOTE_ADDR"])) {
|
||||
$ip = $_SERVER["REMOTE_ADDR"];
|
||||
} else {
|
||||
$ip = "NOT FOUND";
|
||||
}
|
||||
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
|
||||
}
|
||||
|
||||
function verifyReCaptcha($response) {
|
||||
try {
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
||||
$response = $client
|
||||
->request('POST', "https://www.google.com/recaptcha/api/siteverify", [
|
||||
'form_params' => [
|
||||
'secret' => RECAPTCHA_SECRET_KEY,
|
||||
'response' => $response
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$resp = json_decode($response->getBody(), TRUE);
|
||||
if ($resp['success'] === true) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// LDAP handling //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Checks the given credentials against the LDAP server.
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return mixed True if OK, else false or the error code from the server
|
||||
*/
|
||||
function authenticate_user_ldap($username, $password, &$errormsg, &$errorcode) {
|
||||
global $ldap;
|
||||
if (is_empty($username) || is_empty($password)) {
|
||||
return false;
|
||||
}
|
||||
$username = strtolower($username);
|
||||
try {
|
||||
$msg = "";
|
||||
$code = 0;
|
||||
if ($ldap->authenticate($username, $password, $msg, $code) === TRUE) {
|
||||
$errormsg = $msg;
|
||||
$errorcode = $code;
|
||||
return true;
|
||||
} else {
|
||||
$errormsg = $msg;
|
||||
$errorcode = $code;
|
||||
return $msg;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errormsg = $e->getMessage();
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a username exists on the LDAP server.
|
||||
* @global type $ldap_config
|
||||
* @param type $username
|
||||
* @return boolean true if yes, else false
|
||||
*/
|
||||
function user_exists_ldap($username) {
|
||||
global $ldap;
|
||||
try {
|
||||
$username = strtolower($username);
|
||||
$lqb = $ldap->buildLdapQuery();
|
||||
$result = $lqb->fromUsers()
|
||||
->where(['username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($result) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get_account_status_ldap($username, &$error) {
|
||||
global $ldap;
|
||||
try {
|
||||
$username = strtolower($username);
|
||||
$normal = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['enabled' => true, 'passwordMustChange' => false, 'locked' => false, 'disabled' => false, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($normal) == 1) {
|
||||
return "NORMAL";
|
||||
}
|
||||
$disabled = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['disabled' => true, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
$locked = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['locked' => true, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($disabled) == 1 || count($locked) == 1) {
|
||||
return "LOCKED_OR_DISABLED";
|
||||
}
|
||||
$passwordExpired = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['passwordMustChange' => true, 'username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($passwordExpired) == 1) {
|
||||
return "CHANGE_PASSWORD";
|
||||
}
|
||||
$other = $ldap->buildLdapQuery()
|
||||
->fromUsers()
|
||||
->where(['username' => $username])
|
||||
->getLdapQuery()
|
||||
->getResult();
|
||||
if (count($other) == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return "OTHER";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 2-factor authentication //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Check if a user has TOTP setup
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @return boolean true if TOTP secret exists, else false
|
||||
*/
|
||||
function userHasTOTP($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
|
||||
if (is_empty($secret)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a TOTP secret for the given user.
|
||||
* @param string $username
|
||||
* @return string OTP provisioning URI (for generating a QR code)
|
||||
*/
|
||||
function newTOTP($username) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$secret = random_bytes(20);
|
||||
$encoded_secret = Base32::encode($secret);
|
||||
$userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $username])[0];
|
||||
$totp = new TOTP((is_null($userdata['email']) ? $userdata['realname'] : $userdata['email']), $encoded_secret);
|
||||
$totp->setIssuer(SYSTEM_NAME);
|
||||
return $totp->getProvisioningUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a TOTP secret for the user.
|
||||
* @global $database $database
|
||||
* @param string $username
|
||||
* @param string $secret
|
||||
*/
|
||||
function saveTOTP($username, $secret) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$database->update('accounts', ['authsecret' => $secret], ['username' => $username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a TOTP multiauth code
|
||||
* @global $database
|
||||
* @param string $username
|
||||
* @param int $code
|
||||
* @return boolean true if it's legit, else false
|
||||
*/
|
||||
function verifyTOTP($username, $code) {
|
||||
global $database;
|
||||
$username = strtolower($username);
|
||||
$userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
|
||||
if (is_empty($userdata['authsecret'])) {
|
||||
return false;
|
||||
}
|
||||
$totp = new TOTP(null, $userdata['authsecret']);
|
||||
return $totp->verify($code);
|
||||
}
|
@ -2,6 +2,9 @@
|
||||
/*
|
||||
* 500 most common passwords, to be used in stopping idiots from having really bad passwords.
|
||||
* Source: https://github.com/danielmiessler/SecLists/blob/master/Passwords/500-worst-passwords.txt
|
||||
*
|
||||
* This file is licensed under the Creative Commons Attribution ShareAlike 3.0 License.
|
||||
* https://creativecommons.org/licenses/by-sa/3.0/
|
||||
*/
|
||||
|
||||
|
||||
|
169
login/index.php
Normal file
169
login/index.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . "/../required.php";
|
||||
|
||||
|
||||
if (empty($_GET['code']) || empty($_GET['redirect'])) {
|
||||
die("Bad request.");
|
||||
}
|
||||
|
||||
// Delete old keys to keep the table small and tidy
|
||||
$database->delete("userloginkeys", ["expires[<]" => date("Y-m-d H:i:s")]);
|
||||
|
||||
if (!$database->has("userloginkeys", ["AND" => ["key" => $_GET["code"]], "expires[>]" => date("Y-m-d H:i:s"), "uid" => null])) {
|
||||
header("Location: $_GET[redirect]");
|
||||
die("Invalid auth code.");
|
||||
}
|
||||
|
||||
$APPINFO = $database->get("userloginkeys", ["appname", "appicon"], ["key" => $_GET["code"]]);
|
||||
$APPNAME = $APPINFO["appname"];
|
||||
$APPICON = $APPINFO["appicon"];
|
||||
|
||||
if (empty($_SESSION['thisstep'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
}
|
||||
|
||||
if (!empty($_GET['reset'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
$_SESSION['check'] = "";
|
||||
header("Location: ./?code=$_GET[code]&redirect=$_GET[redirect]");
|
||||
}
|
||||
|
||||
$error = "";
|
||||
|
||||
function sendUserBack($code, $url, $uid) {
|
||||
global $database;
|
||||
$_SESSION['check'] = null;
|
||||
$_SESSION['thisstep'] = null;
|
||||
$_SESSION['login_uid'] = null;
|
||||
$_SESSION['login_pwd'] = null;
|
||||
$database->update("userloginkeys", ["uid" => $uid], ["key" => $code]);
|
||||
Log::insert(LogType::LOGIN_OK, $uid);
|
||||
header("Location: $url");
|
||||
die("<a href=\"" . htmlspecialchars($url) . "\">Click here</a>");
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['check'])) {
|
||||
switch ($_SESSION['check']) {
|
||||
case "username":
|
||||
if (empty($_POST['username'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
break;
|
||||
}
|
||||
$user = User::byUsername($_POST['username']);
|
||||
if ($user->exists()) {
|
||||
$_SESSION['login_uid'] = $user->getUID();
|
||||
switch ($user->getStatus()->get()) {
|
||||
case AccountStatus::LOCKED_OR_DISABLED:
|
||||
$error = $Strings->get("account locked", false);
|
||||
break;
|
||||
case AccountStatus::TERMINATED:
|
||||
$error = $Strings->get("account terminated", false);
|
||||
break;
|
||||
case AccountStatus::ALERT_ON_ACCESS:
|
||||
$mail_resp = $user->sendAlertEmail();
|
||||
case AccountStatus::NORMAL:
|
||||
$_SESSION['thisstep'] = "password";
|
||||
break;
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
$_SESSION['thisstep'] = "change_password";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$error = $Strings->get("Username not found.", false);
|
||||
Log::insert(LogType::LOGIN_FAILED, null, "Username: " . $user->getUsername());
|
||||
}
|
||||
break;
|
||||
case "password":
|
||||
if (empty($_POST['password'])) {
|
||||
$_SESSION['thisstep'] = "password";
|
||||
break;
|
||||
}
|
||||
if (empty($_SESSION['login_uid'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
break;
|
||||
}
|
||||
$user = new User($_SESSION['login_uid']);
|
||||
if ($user->checkPassword($_POST['password'])) {
|
||||
$_SESSION['login_pwd'] = true;
|
||||
if ($user->has2fa()) {
|
||||
$_SESSION['thisstep'] = "totp";
|
||||
} else {
|
||||
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
|
||||
}
|
||||
} else {
|
||||
$error = $Strings->get("Password incorrect.", false);
|
||||
if ($user->checkAppPassword($_POST['password'])) {
|
||||
$error = $Strings->get("App passwords are not allowed here.", false);
|
||||
}
|
||||
Log::insert(LogType::LOGIN_FAILED, $user);
|
||||
}
|
||||
break;
|
||||
case "change_password":
|
||||
if (empty($_POST['oldpassword']) || empty($_POST['newpassword']) || empty($_POST['newpassword2'])) {
|
||||
$_SESSION['thisstep'] = "change_password";
|
||||
$error = $Strings->get("Fill in all three boxes.", false);
|
||||
break;
|
||||
}
|
||||
|
||||
$user = new User($_SESSION['login_uid']);
|
||||
try {
|
||||
$result = $user->changePassword($_POST['oldpassword'], $_POST['newpassword'], $_POST['newpassword2']);
|
||||
|
||||
if ($result === TRUE) {
|
||||
if ($user->has2fa()) {
|
||||
$_SESSION['thisstep'] = "totp";
|
||||
} else {
|
||||
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
|
||||
}
|
||||
}
|
||||
} catch (PasswordMatchException $e) {
|
||||
$error = $Strings->get(MESSAGES["passwords_same"]["string"], false);
|
||||
} catch (PasswordMismatchException $e) {
|
||||
$error = $Strings->get(MESSAGES["new_password_mismatch"]["string"], false);
|
||||
} catch (IncorrectPasswordException $e) {
|
||||
$error = $Strings->get(MESSAGES["old_password_mismatch"]["string"], false);
|
||||
} catch (WeakPasswordException $e) {
|
||||
$error = $Strings->get(MESSAGES["weak_password"]["string"], false);
|
||||
}
|
||||
break;
|
||||
case "totp":
|
||||
if (empty($_POST['totp']) || empty($_SESSION['login_uid'])) {
|
||||
$_SESSION['thisstep'] = "username";
|
||||
break;
|
||||
}
|
||||
$user = new User($_SESSION['login_uid']);
|
||||
if ($user->check2fa($_POST['totp'])) {
|
||||
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
|
||||
} else {
|
||||
$error = $Strings->get("Code incorrect.", false);
|
||||
Log::insert(LogType::BAD_2FA, null, "Username: " . $user->getUsername());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . "/parts/header.php";
|
||||
|
||||
switch ($_SESSION['thisstep']) {
|
||||
case "username":
|
||||
require __DIR__ . "/parts/username.php";
|
||||
break;
|
||||
case "password":
|
||||
require __DIR__ . "/parts/password.php";
|
||||
break;
|
||||
case "change_password":
|
||||
require __DIR__ . "/parts/change_password.php";
|
||||
break;
|
||||
case "totp":
|
||||
require __DIR__ . "/parts/totp.php";
|
||||
break;
|
||||
}
|
||||
|
||||
include __DIR__ . "/parts/footer.php";
|
51
login/parts/change_password.php
Normal file
51
login/parts/change_password.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "change_password";
|
||||
$username = (new User($_SESSION['login_uid']))->getUsername();
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<?php $Strings->get("password expired"); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="oldpassword"><?php $Strings->build("Current password for {user}", ["user" => htmlentities($username)]); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="oldpassword" name="oldpassword" placeholder="" required autofocus>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="newpassword"><?php $Strings->get("New password"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="newpassword" name="newpassword" placeholder="" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="newpassword2"><?php $Strings->get("New password (again)"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="newpassword2" name="newpassword2" placeholder="" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary ml-auto">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
15
login/parts/footer.php
Normal file
15
login/parts/footer.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script src="../static/js/fontawesome-all.min.js"></script>
|
59
login/parts/header.php
Normal file
59
login/parts/header.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
header("Link: <../static/fonts/Roboto.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/css/bootstrap.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/css/login.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/css/svg-with-js.min.css>; rel=preload; as=style", false);
|
||||
header("Link: <../static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
<link rel="icon" href="../static/img/logo.svg">
|
||||
|
||||
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../static/css/login.css" rel="stylesheet">
|
||||
<link href="../static/css/svg-with-js.min.css" rel="stylesheet">
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<?php
|
||||
if (!empty($APPICON)) {
|
||||
?>
|
||||
<div class="col-12 text-center">
|
||||
<img class="banner-image" src="<?php echo $APPICON; ?>" />
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<div class="col-12">
|
||||
<div class="blank-image"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 mb-4"><?php $Strings->build("Login to {app}", ["app" => htmlentities($APPNAME)]); ?></h1>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-8 col-lg-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<?php
|
||||
if (!empty($error)) {
|
||||
?>
|
||||
<div class="text-danger">
|
||||
<?php echo $error; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
32
login/parts/password.php
Normal file
32
login/parts/password.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "password";
|
||||
$username = (new User($_SESSION['login_uid']))->getUsername();
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="password"><?php $Strings->build("Password for {user}", ["user" => htmlentities($username)]); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
||||
</div>
|
||||
<input type="password" class="form-control" id="password" name="password" aria-describedby="passwordHelp" placeholder="" required autofocus>
|
||||
</div>
|
||||
<small id="passwordHelp" class="form-text text-muted">Enter your password.</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<a href="./?code=<?php echo htmlentities($_GET['code']); ?>&redirect=<?php echo htmlentities($_GET['redirect']); ?>&reset=1" class="btn btn-link mr-2">
|
||||
<i class="fas fa-chevron-left"></i> <?php $Strings->get("Back"); ?>
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary ml-auto">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
31
login/parts/totp.php
Normal file
31
login/parts/totp.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "totp";
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="totp"><?php $Strings->get("Two-factor code"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-mobile-alt"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="totp" name="totp" aria-describedby="totpHelp" placeholder="" required autofocus>
|
||||
</div>
|
||||
<small id="passwordHelp" class="form-text text-muted">Enter the two-factor code from your mobile device.</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<a href="./?code=<?php echo htmlentities($_GET['code']); ?>&redirect=<?php echo htmlentities($_GET['redirect']); ?>&reset=1" class="btn btn-link mr-2">
|
||||
<i class="fas fa-chevron-left"></i> <?php $Strings->get("Back"); ?>
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary ml-auto">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
37
login/parts/username.php
Normal file
37
login/parts/username.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$_SESSION['check'] = "username";
|
||||
?>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="username"><?php $Strings->get("username"); ?></label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-user"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="username" name="username" aria-describedby="usernameHelp" placeholder="" required autofocus>
|
||||
</div>
|
||||
<small id="usernameHelp" class="form-text text-muted">Enter your username.</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<div class="ml-auto">
|
||||
<?php
|
||||
if ($SETTINGS['signups_enabled'] === true) {
|
||||
?>
|
||||
<a href="../signup/?code=<?php echo urlencode($_GET["code"]); ?>&redirect=<?php echo urlencode($_GET["redirect"]); ?>" class="btn btn-link mr-2"><?php $Strings->get("Create Account"); ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
244
mobile/index.php
Normal file
244
mobile/index.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Mobile app API
|
||||
*/
|
||||
|
||||
require __DIR__ . "/../required.php";
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// Allow ping check without authentication
|
||||
if ($VARS['action'] == "ping") {
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
}
|
||||
|
||||
if ($SETTINGS['mobile_enabled'] !== TRUE) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
|
||||
}
|
||||
|
||||
// Make sure we have a username and access key
|
||||
if (empty($VARS['username']) || empty($VARS['key'])) {
|
||||
http_response_code(401);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
|
||||
}
|
||||
|
||||
$username = strtolower($VARS['username']);
|
||||
$key = strtoupper($VARS['key']);
|
||||
|
||||
// Make sure the username and key are actually legit
|
||||
// Don't check key if we're trying to generate one
|
||||
if ($VARS['action'] == "generatesynccode") {
|
||||
if (!User::byUsername($username)->exists()) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
|
||||
}
|
||||
} else {
|
||||
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $key, 'accounts.username' => $username]]);
|
||||
if ($user_key_valid !== TRUE) {
|
||||
engageRateLimit();
|
||||
Log::insert(LogType::MOBILE_BAD_KEY, null, "Username: " . $username . ", Key: " . $key);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
|
||||
}
|
||||
}
|
||||
|
||||
// Obscure key
|
||||
if (strlen($key) > 7) {
|
||||
for ($i = 3; $i < strlen($key) - 3; $i++) {
|
||||
$key[$i] = "*";
|
||||
}
|
||||
}
|
||||
|
||||
// Process the action
|
||||
switch ($VARS['action']) {
|
||||
case "check_key":
|
||||
// Check if the username/key combo is valid.
|
||||
// If we get this far, it is, so return success.
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
case "check_password":
|
||||
// Check if the user-supplied password is valid.
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
|
||||
}
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
|
||||
exit(json_encode(["status" => "OK", "uid" => $user->getUID()]));
|
||||
} else {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
case "user_info":
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
|
||||
}
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
$userinfo = ["uid" => $user->getUID(), "username" => $user->getUsername(), "realname" => $user->getName(), "email" => $user->getEmail()];
|
||||
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
|
||||
exit(json_encode(["status" => "OK", "info" => $userinfo]));
|
||||
} else {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
case "start_session":
|
||||
// Do a web login.
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
if ($user->exists()) {
|
||||
if ($user->getStatus()->get() == AccountStatus::NORMAL) {
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Session::start($user);
|
||||
$_SESSION['mobile'] = true;
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
case "listapps":
|
||||
$apps = $SETTINGS['apps'];
|
||||
// Format paths as absolute URLs
|
||||
foreach ($apps as $k => $v) {
|
||||
if (strpos($apps[$k]['url'], "http") === FALSE) {
|
||||
$apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url'];
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "apps" => $apps]));
|
||||
case "gencode":
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($username);
|
||||
$code = "";
|
||||
do {
|
||||
$code = random_int(100000, 999999);
|
||||
} while ($database->has("onetimekeys", ["key" => $code]));
|
||||
|
||||
$database->insert("onetimekeys", ["key" => $code, "uid" => $user->getUID(), "expires" => date("Y-m-d H:i:s", strtotime("+1 minute"))]);
|
||||
|
||||
$database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
|
||||
exit(json_encode(["status" => "OK", "code" => $code]));
|
||||
case "checknotifications":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
try {
|
||||
$notifications = Notifications::get($user, false);
|
||||
exit(json_encode(["status" => "OK", "notifications" => $notifications]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "readnotification":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (empty($VARS['id'])) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
|
||||
}
|
||||
try {
|
||||
Notifications::read($user, $VARS['id']);
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "addnotification":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
try {
|
||||
$timestamp = "";
|
||||
if (!empty($VARS['timestamp'])) {
|
||||
$timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
|
||||
}
|
||||
$url = "";
|
||||
if (!empty($VARS['url'])) {
|
||||
$url = $VARS['url'];
|
||||
}
|
||||
$nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
|
||||
|
||||
exit(json_encode(["status" => "OK", "id" => $nid]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "deletenotification":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
if (empty($VARS['id'])) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
|
||||
}
|
||||
try {
|
||||
Notifications::delete($user, $VARS['id']);
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
} catch (Exception $ex) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
|
||||
}
|
||||
break;
|
||||
case "hasotp":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
exit(json_encode(["status" => "OK", "otp" => $user->has2fa()]));
|
||||
break;
|
||||
case "generatesynccode":
|
||||
$user = User::byUsername($username);
|
||||
if ($user->has2fa()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("2-factor is enabled, you need to use the QR code or manual setup for security reasons", false)]));
|
||||
}
|
||||
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
|
||||
}
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($VARS['desc']);
|
||||
$database->insert('mobile_codes', ['uid' => $user->getUID(), 'code' => $code, 'description' => $desc]);
|
||||
exit(json_encode(["status" => "OK", "code" => $code]));
|
||||
} else {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
default:
|
||||
http_response_code(404);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));
|
||||
}
|
9
nbproject/mplheader.txt
Normal file
9
nbproject/mplheader.txt
Normal file
@ -0,0 +1,9 @@
|
||||
<#if licenseFirst??>
|
||||
${licenseFirst}
|
||||
</#if>
|
||||
${licensePrefix}This Source Code Form is subject to the terms of the Mozilla Public
|
||||
${licensePrefix}License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
${licensePrefix}file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
<#if licenseLast??>
|
||||
${licenseLast}
|
||||
</#if>
|
@ -1,5 +1,6 @@
|
||||
include.path=${php.global.include.path}
|
||||
php.version=PHP_70
|
||||
project.licensePath=./nbproject/mplheader.txt
|
||||
source.encoding=UTF-8
|
||||
src.dir=.
|
||||
tags.asp=false
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user