├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── api ├── group │ └── group.go └── user │ └── user.go ├── cmd ├── group.go ├── root.go └── user.go ├── docs └── start-ldap-eryajf.sh ├── go.mod ├── go.sum ├── main.go └── public ├── group.go ├── public.go └── user.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | ldapctl 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | # Go workspace file 22 | go.work 23 | 24 | # ide 25 | .vscode 26 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | build: 4 | go build -o ldapctl main.go 5 | 6 | build-linux: 7 | CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o ldapctl main.go 8 | 9 | build-linux-arm: 10 | CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build -o ldapctl main.go -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | **ldapctl** is a openLDAP CLI based on [ldap](https://github.com/go-ldap/ldap) library. 🚀 3 | 4 | It only takes two steps to start the ldapctl experience: 5 | 6 | 1. use docs/start-ldap-eryajf.sh script Start a openLDAP instance locally through docker。 7 | 2. Run the make build compilation project, and then you can test it.。 8 | 9 | If you want to manage your ldap, you can directly modify the configuration information in public/public.go, then compile the project and put it into use. 10 | 11 | At present, it provides simple management of users and groups, which is mainly used to learn ldap library. There may be some deficiencies. Welcome to communicate. 12 | 13 | - user 14 | - add: add user 15 | - changeuserpwd: update user uid 16 | - checkuser: Check whether the user password is correct 17 | - delete: delete user by uid 18 | - get: get user by uid 19 | - getall: list all users 20 | - update: update user 21 | - updateuserdn: update user uid 22 | 23 | - group 24 | - add: add group 25 | - adduser: add user to group 26 | - delete: delete group 27 | - get: get group menbers 28 | - getall: list all groups 29 | - removeuser: remove user to group 30 | - update: update group 31 | 32 | 33 | If you have other functions you want to add, please submit them in issue. -------------------------------------------------------------------------------- /api/group/group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eryajf/ldapctl/public" 7 | 8 | "github.com/liushuochen/gotable" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // GetAllGroupsCmd 13 | // ./ldapctl group getall 14 | var GetAllGroupsCmd = &cobra.Command{ 15 | Use: "getall", 16 | Short: "list all groups", 17 | Long: `list all groups, No parameters are required`, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | groups, err := public.GetAllGroup() 20 | if err != nil { 21 | fmt.Printf("get all group failed: %v\n", err) 22 | return 23 | } 24 | 25 | tb, err := gotable.Create("cn", "description") 26 | if err != nil { 27 | fmt.Printf("create table failed: %v\n", err) 28 | return 29 | } 30 | for _, group := range groups { 31 | row := make(map[string]string) 32 | row["cn"] = group.CN 33 | row["description"] = group.Desc 34 | tb.AddRow(row) 35 | } 36 | fmt.Println(tb) 37 | }, 38 | } 39 | 40 | // GetGroupMenbersCmd 41 | // ./ldapctl group get -c groupname 42 | var GetGroupMenbersCmd = &cobra.Command{ 43 | Use: "get", 44 | Short: "get group menbers", 45 | Long: `get group menbers`, 46 | Run: func(cmd *cobra.Command, args []string) { 47 | cn, _ := cmd.Flags().GetString("cn") 48 | members, err := public.GetGroupMenber(cn) 49 | if err != nil { 50 | fmt.Printf("get all group failed: %v\n", err) 51 | return 52 | } 53 | for _, member := range members { 54 | fmt.Println(member) 55 | } 56 | }, 57 | } 58 | 59 | // AddGroupCmd 60 | // ./ldapctl group add -c testg -d "测试分组" 61 | var AddGroupCmd = &cobra.Command{ 62 | Use: "add", 63 | Short: "add group", 64 | Long: `add group`, 65 | Run: func(cmd *cobra.Command, args []string) { 66 | cn, _ := cmd.Flags().GetString("cn") 67 | desc, _ := cmd.Flags().GetString("desc") 68 | err := public.AddGroup(public.Group{ 69 | CN: cn, 70 | Desc: desc, 71 | }) 72 | if err != nil { 73 | fmt.Printf("add group failed: %v\n", err) 74 | return 75 | } else { 76 | fmt.Println("add group success") 77 | } 78 | }, 79 | } 80 | 81 | // UpdateGroupCmd 82 | // ./ldapctl group update -c testg -d "测试分组1" 83 | var UpdateGroupCmd = &cobra.Command{ 84 | Use: "update", 85 | Short: "update group", 86 | Long: `update group`, 87 | Run: func(cmd *cobra.Command, args []string) { 88 | cn, _ := cmd.Flags().GetString("cn") 89 | desc, _ := cmd.Flags().GetString("desc") 90 | err := public.UpdateGroup(public.Group{ 91 | CN: cn, 92 | Desc: desc, 93 | }) 94 | if err != nil { 95 | fmt.Printf("update group failed: %v\n", err) 96 | return 97 | } else { 98 | fmt.Println("update group success") 99 | } 100 | }, 101 | } 102 | 103 | // DeleteGroupCmd 104 | // ./ldapctl group delete -c testg 105 | var DeleteGroupCmd = &cobra.Command{ 106 | Use: "delete", 107 | Short: "delete group", 108 | Long: `delete group`, 109 | Run: func(cmd *cobra.Command, args []string) { 110 | cn, _ := cmd.Flags().GetString("cn") 111 | err := public.DelGroup(cn) 112 | if err != nil { 113 | fmt.Printf("delete group failed: %v\n", err) 114 | return 115 | } else { 116 | fmt.Println("delete group success") 117 | } 118 | }, 119 | } 120 | 121 | // AddUserToGroupCmd 122 | // ./ldapctl group adduser -c test -u eryajf 123 | var AddUserToGroupCmd = &cobra.Command{ 124 | Use: "adduser", 125 | Short: "add user to group", 126 | Long: `add user to group`, 127 | Run: func(cmd *cobra.Command, args []string) { 128 | cn, _ := cmd.Flags().GetString("cn") 129 | user, _ := cmd.Flags().GetString("user") 130 | err := public.AddUserToGroup(user, cn) 131 | if err != nil { 132 | fmt.Printf("add user to group: %v\n", err) 133 | return 134 | } else { 135 | fmt.Println("add user to group success") 136 | } 137 | }, 138 | } 139 | 140 | // RemoveUserFromGroupCmd 141 | // ./ldapctl group removeuser -c test -u eryajf 142 | var RemoveUserFromGroupCmd = &cobra.Command{ 143 | Use: "removeuser", 144 | Short: "remove user to group", 145 | Long: `remove user to group`, 146 | Run: func(cmd *cobra.Command, args []string) { 147 | cn, _ := cmd.Flags().GetString("cn") 148 | user, _ := cmd.Flags().GetString("user") 149 | err := public.RemoveUserFromGroup(user, cn) 150 | if err != nil { 151 | fmt.Printf("remove user to group: %v\n", err) 152 | return 153 | } else { 154 | fmt.Println("remove user to group success") 155 | } 156 | }, 157 | } 158 | -------------------------------------------------------------------------------- /api/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eryajf/ldapctl/public" 7 | 8 | "github.com/liushuochen/gotable" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // AddUserCmd 13 | // ./ldapctl user add --cn testuser1 --sn 测试用户1 --employeeNumber 001 --mail testuer1@eryajf.net --mobile 13888888881 --uid testuser1 --userPassword testuser1 14 | var AddUserCmd = &cobra.Command{ 15 | Use: "add", 16 | Short: "add user", 17 | Long: `Add user actions`, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | cn, _ := cmd.Flags().GetString("cn") 20 | sn, _ := cmd.Flags().GetString("sn") 21 | businessCategory, _ := cmd.Flags().GetString("businessCategory") 22 | departmentNumber, _ := cmd.Flags().GetString("departmentNumber") 23 | description, _ := cmd.Flags().GetString("desc") 24 | displayName, _ := cmd.Flags().GetString("displayName") 25 | mail, _ := cmd.Flags().GetString("mail") 26 | employeeNumber, _ := cmd.Flags().GetString("employeeNumber") 27 | givenName, _ := cmd.Flags().GetString("givenName") 28 | postalAddress, _ := cmd.Flags().GetString("postalAddress") 29 | mobile, _ := cmd.Flags().GetString("mobile") 30 | userPassword, _ := cmd.Flags().GetString("userPassword") 31 | 32 | uid, _ := cmd.Flags().GetString("uid") 33 | if businessCategory == "" { 34 | businessCategory = "Undefined" 35 | } 36 | if departmentNumber == "" { 37 | departmentNumber = "Undefined" 38 | } 39 | if description == "" { 40 | description = "Undefined" 41 | } 42 | if displayName == "" { 43 | displayName = "Undefined" 44 | } 45 | if givenName == "" { 46 | givenName = "Undefined" 47 | } 48 | if postalAddress == "" { 49 | postalAddress = "Undefined" 50 | } 51 | 52 | err := public.AddUser(public.User{ 53 | CN: cn, 54 | SN: sn, 55 | BusinessCategory: businessCategory, 56 | DepartmentNumber: departmentNumber, 57 | Description: description, 58 | DisplayName: displayName, 59 | Mail: mail, 60 | EmployeeNumber: employeeNumber, 61 | GivenName: givenName, 62 | PostalAddress: postalAddress, 63 | Mobile: mobile, 64 | UID: uid, 65 | Password: userPassword, 66 | }) 67 | if err != nil { 68 | fmt.Printf("add user failed, err: %v\n", err) 69 | } else { 70 | fmt.Println("add user success") 71 | } 72 | }, 73 | } 74 | 75 | // UpdateUserCmd 76 | // ./ldapctl user update -u testuser1 --displayName testtest 77 | var UpdateUserCmd = &cobra.Command{ 78 | Use: "update", 79 | Short: "update user", 80 | Long: `Update user actions`, 81 | Run: func(cmd *cobra.Command, args []string) { 82 | cn, _ := cmd.Flags().GetString("cn") 83 | sn, _ := cmd.Flags().GetString("sn") 84 | businessCategory, _ := cmd.Flags().GetString("businessCategory") 85 | departmentNumber, _ := cmd.Flags().GetString("departmentNumber") 86 | description, _ := cmd.Flags().GetString("description") 87 | displayName, _ := cmd.Flags().GetString("displayName") 88 | mail, _ := cmd.Flags().GetString("mail") 89 | employeeNumber, _ := cmd.Flags().GetString("employeeNumber") 90 | givenName, _ := cmd.Flags().GetString("givenName") 91 | postalAddress, _ := cmd.Flags().GetString("postalAddress") 92 | mobile, _ := cmd.Flags().GetString("mobild") 93 | uid, _ := cmd.Flags().GetString("uid") 94 | 95 | user, err := public.GetUserByUID(uid) 96 | if err != nil { 97 | fmt.Printf("get user failed, err: %v\n", err) 98 | } 99 | if cn == "" { 100 | cn = user.CN 101 | } 102 | if sn == "" { 103 | sn = user.SN 104 | } 105 | if businessCategory == "" { 106 | businessCategory = user.BusinessCategory 107 | } 108 | if departmentNumber == "" { 109 | departmentNumber = user.DepartmentNumber 110 | } 111 | if description == "" { 112 | description = user.Description 113 | } 114 | if displayName == "" { 115 | displayName = user.DisplayName 116 | } 117 | if mail == "" { 118 | mail = user.Mail 119 | } 120 | if employeeNumber == "" { 121 | employeeNumber = user.EmployeeNumber 122 | } 123 | if givenName == "" { 124 | givenName = user.GivenName 125 | } 126 | if postalAddress == "" { 127 | postalAddress = user.PostalAddress 128 | } 129 | if mobile == "" { 130 | mobile = user.Mobile 131 | } 132 | 133 | err = public.UpdateUser(public.User{ 134 | CN: cn, 135 | SN: sn, 136 | BusinessCategory: businessCategory, 137 | DepartmentNumber: departmentNumber, 138 | Description: description, 139 | DisplayName: displayName, 140 | Mail: mail, 141 | EmployeeNumber: employeeNumber, 142 | GivenName: givenName, 143 | PostalAddress: postalAddress, 144 | Mobile: mobile, 145 | UID: uid, 146 | }) 147 | if err != nil { 148 | fmt.Printf("add user failed, err: %v\n", err) 149 | } else { 150 | fmt.Println("update user success") 151 | } 152 | }, 153 | } 154 | 155 | // GetAllUsersCmd 156 | // ./ldapctl user getall 157 | var GetAllUsersCmd = &cobra.Command{ 158 | Use: "getall", 159 | Short: "list all users", 160 | Long: `list all users, No parameters are required`, 161 | Run: func(cmd *cobra.Command, args []string) { 162 | users, err := public.GetAllUser() 163 | if err != nil { 164 | fmt.Printf("get all user failed: %v\n", err) 165 | return 166 | } 167 | tb, err := gotable.Create("cn", "sn", "businessCategory", "departmentNumber", "description", "displayName", "mail", "employeeNumber", "givenName", "postalAddress", "mobile", "uid", "userPassword") 168 | if err != nil { 169 | fmt.Printf("create table failed: %v\n", err) 170 | return 171 | } 172 | for _, user := range users { 173 | row := make(map[string]string) 174 | row["cn"] = user.CN 175 | row["sn"] = user.SN 176 | row["businessCategory"] = user.BusinessCategory 177 | row["departmentNumber"] = user.DepartmentNumber 178 | row["description"] = user.Description 179 | row["displayName"] = user.DisplayName 180 | row["mail"] = user.Mail 181 | row["employeeNumber"] = user.EmployeeNumber 182 | row["givenName"] = user.GivenName 183 | row["postalAddress"] = user.PostalAddress 184 | row["mobile"] = user.Mobile 185 | row["uid"] = user.UID 186 | row["userPassword"] = user.Password 187 | tb.AddRow(row) 188 | } 189 | fmt.Println(tb) 190 | }, 191 | } 192 | 193 | // GetUserByUIDCmd 194 | // ./ldapctl user get -u testuser1 195 | var GetUserByUIDCmd = &cobra.Command{ 196 | Use: "get", 197 | Short: "get user by uid", 198 | Long: `get user by uid`, 199 | Run: func(cmd *cobra.Command, args []string) { 200 | uid, _ := cmd.Flags().GetString("uid") 201 | user, err := public.GetUserByUID(uid) 202 | if err != nil { 203 | fmt.Printf("get all user failed: %v\n", err) 204 | return 205 | } 206 | tb, err := gotable.Create("cn", "sn", "businessCategory", "departmentNumber", "description", "displayName", "mail", "employeeNumber", "givenName", "postalAddress", "mobile", "uid", "userPassword") 207 | if err != nil { 208 | fmt.Printf("create table failed: %v\n", err) 209 | return 210 | } 211 | row := make(map[string]string) 212 | row["cn"] = user.CN 213 | row["sn"] = user.SN 214 | row["businessCategory"] = user.BusinessCategory 215 | row["departmentNumber"] = user.DepartmentNumber 216 | row["description"] = user.Description 217 | row["displayName"] = user.DisplayName 218 | row["mail"] = user.Mail 219 | row["employeeNumber"] = user.EmployeeNumber 220 | row["givenName"] = user.GivenName 221 | row["postalAddress"] = user.PostalAddress 222 | row["mobile"] = user.Mobile 223 | row["uid"] = user.UID 224 | row["userPassword"] = user.Password 225 | tb.AddRow(row) 226 | fmt.Println(tb) 227 | }, 228 | } 229 | 230 | // DelUserByUIDCmd 231 | // ./ldapctl user del -u testuser1 232 | var DelUserByUIDCmd = &cobra.Command{ 233 | Use: "delete", 234 | Short: "delete user by uid", 235 | Long: `delete user by uid`, 236 | Run: func(cmd *cobra.Command, args []string) { 237 | uid, _ := cmd.Flags().GetString("uid") 238 | err := public.DelUser(uid) 239 | if err != nil { 240 | fmt.Printf("delete user %s failed: %v\n", uid, err) 241 | } else { 242 | fmt.Println("delete user success") 243 | } 244 | }, 245 | } 246 | 247 | // CheckUserPassCmd 248 | // ./ldapctl user checkuser -u testuser1 -p testuser1 249 | var CheckUserPassCmd = &cobra.Command{ 250 | Use: "checkuser", 251 | Short: "Check whether the user password is correct", 252 | Long: `Check whether the user password is correct`, 253 | Run: func(cmd *cobra.Command, args []string) { 254 | username, _ := cmd.Flags().GetString("uid") 255 | pwd, _ := cmd.Flags().GetString("pwd") 256 | err := public.CheckUser(username, pwd) 257 | if err != nil { 258 | fmt.Printf("check user %s password failed: %v\n", username, err) 259 | } else { 260 | fmt.Println("check user password success") 261 | } 262 | }, 263 | } 264 | 265 | // UpdataUserDNCmd 266 | // ./ldapctl user updateuserdn -o testuser1 -n user1 267 | var UpdataUserDNCmd = &cobra.Command{ 268 | Use: "updateuserdn", 269 | Short: "update user uid", 270 | Long: `update user uid`, 271 | Run: func(cmd *cobra.Command, args []string) { 272 | olduid, _ := cmd.Flags().GetString("olduid") 273 | newuid, _ := cmd.Flags().GetString("newuid") 274 | err := public.UpdateUserDN(olduid, newuid) 275 | if err != nil { 276 | fmt.Printf("check user %s password failed: %v\n", olduid, err) 277 | } 278 | }, 279 | } 280 | 281 | // ChangeUserPwdCmd 282 | // ./ldapctl user changeuserpwd -u testuser1 -o testuser1 -n user1 283 | var ChangeUserPwdCmd = &cobra.Command{ 284 | Use: "changeuserpwd", 285 | Short: "update user uid", 286 | Long: `update user uid`, 287 | Run: func(cmd *cobra.Command, args []string) { 288 | uid, _ := cmd.Flags().GetString("uid") 289 | oldpwd, _ := cmd.Flags().GetString("oldpwd") 290 | newpwd, _ := cmd.Flags().GetString("newpwd") 291 | pwd, err := public.ModifyUserPassword(uid, oldpwd, newpwd) 292 | if err != nil { 293 | fmt.Printf("check user %s password failed: %v\n", uid, err) 294 | } else { 295 | fmt.Println("change user password success") 296 | fmt.Printf("new password is %s\n", pwd) 297 | } 298 | }, 299 | } 300 | -------------------------------------------------------------------------------- /cmd/group.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 eryajf Linuxlql@163.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/eryajf/ldapctl/api/group" 20 | 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | // groupCmd represents the jenkins command 25 | var groupCmd = &cobra.Command{ 26 | Use: "group", 27 | Long: `Group-related operations`, 28 | } 29 | 30 | func init() { 31 | rootCmd.AddCommand(groupCmd) 32 | 33 | // get all groups 34 | groupCmd.AddCommand(group.GetAllGroupsCmd) 35 | 36 | // get all group menbers 37 | groupCmd.AddCommand(group.GetGroupMenbersCmd) 38 | set := group.GetGroupMenbersCmd.Flags() 39 | set.StringP("cn", "c", "", "group cn") 40 | group.GetGroupMenbersCmd.MarkFlagRequired("group") 41 | 42 | // add group 43 | groupCmd.AddCommand(group.AddGroupCmd) 44 | aset := group.AddGroupCmd.Flags() 45 | aset.StringP("cn", "c", "", "group cn, It is usually a group name") 46 | aset.StringP("desc", "d", "", "group description") 47 | group.AddGroupCmd.MarkFlagRequired("cn") 48 | group.AddGroupCmd.MarkFlagRequired("desc") 49 | 50 | // update group 51 | groupCmd.AddCommand(group.UpdateGroupCmd) 52 | bset := group.UpdateGroupCmd.Flags() 53 | bset.StringP("cn", "c", "", "group cn, When updating the group, cn filters the group as the unique ID of the group.") 54 | bset.StringP("desc", "d", "", "group description") 55 | group.UpdateGroupCmd.MarkFlagRequired("cn") 56 | group.UpdateGroupCmd.MarkFlagRequired("desc") 57 | 58 | // delete group 59 | groupCmd.AddCommand(group.DeleteGroupCmd) 60 | cset := group.DeleteGroupCmd.Flags() 61 | cset.StringP("cn", "c", "", "group cn, When deleting the group, cn filters the group as the unique ID of the group.") 62 | group.UpdateGroupCmd.MarkFlagRequired("cn") 63 | 64 | // add user to group 65 | groupCmd.AddCommand(group.AddUserToGroupCmd) 66 | dset := group.AddUserToGroupCmd.Flags() 67 | dset.StringP("cn", "c", "", "group cn") 68 | dset.StringP("user", "u", "", "user uid") 69 | group.AddUserToGroupCmd.MarkFlagRequired("cn") 70 | group.AddUserToGroupCmd.MarkFlagRequired("user") 71 | 72 | // remove user to group 73 | groupCmd.AddCommand(group.RemoveUserFromGroupCmd) 74 | eset := group.RemoveUserFromGroupCmd.Flags() 75 | eset.StringP("cn", "c", "", "group cn") 76 | eset.StringP("user", "u", "", "user uid") 77 | group.RemoveUserFromGroupCmd.MarkFlagRequired("cn") 78 | group.RemoveUserFromGroupCmd.MarkFlagRequired("user") 79 | } 80 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 eryajf Linuxlql@163.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | // rootCmd represents the base command when called without any subcommands 23 | var rootCmd = &cobra.Command{ 24 | Use: "ldapctl", 25 | Short: "A Client For Ldap", 26 | Version: "v0.0.1", 27 | Long: `The ldap client manages the resources of the ldap.`, 28 | } 29 | 30 | // Execute adds all child commands to the root command and sets flags appropriately. 31 | // This is called by main.main(). It only needs to happen once to the rootCmd. 32 | func Execute() { 33 | cobra.CheckErr(rootCmd.Execute()) 34 | } 35 | 36 | func init() { 37 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 38 | } 39 | -------------------------------------------------------------------------------- /cmd/user.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 eryajf Linuxlql@163.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/eryajf/ldapctl/api/user" 20 | 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | // userCmd represents the jenkins command 25 | var userCmd = &cobra.Command{ 26 | Use: "user", 27 | Long: `User-related operations`, 28 | } 29 | 30 | func init() { 31 | rootCmd.AddCommand(userCmd) 32 | 33 | // get all users 34 | userCmd.AddCommand(user.GetAllUsersCmd) 35 | 36 | // add user 37 | userCmd.AddCommand(user.AddUserCmd) 38 | aset := user.AddUserCmd.Flags() 39 | aset.StringP("cn", "c", "", "user cn, It is usually a user name, adjusted according to the actual situation of your own use.") 40 | aset.StringP("sn", "s", "", "user sn, Usually a surname, adjusted according to the actual situation of your own use") 41 | aset.StringP("businessCategory", "b", "", "user businessCategory") 42 | aset.StringP("departmentNumber", "d", "", "user departmentNumber") 43 | aset.StringP("desc", "", "", "user description") 44 | aset.StringP("displayName", "", "", "user displayName") 45 | aset.StringP("mail", "m", "", "user mail") 46 | aset.StringP("employeeNumber", "e", "", "user employeeNumber") 47 | aset.StringP("givenName", "g", "", "user givenName") 48 | aset.StringP("postalAddress", "a", "", "user postalAddress") 49 | aset.StringP("mobile", "", "", "user mobile") 50 | aset.StringP("uid", "u", "", "user uid,Usually consistent with sn, can be adjusted according to the actual situation") 51 | aset.StringP("userPassword", "p", "", "userPassword") 52 | user.AddUserCmd.MarkFlagRequired("cn") 53 | user.AddUserCmd.MarkFlagRequired("sn") 54 | user.AddUserCmd.MarkFlagRequired("mail") 55 | user.AddUserCmd.MarkFlagRequired("employeeNumber") 56 | user.AddUserCmd.MarkFlagRequired("mobile") 57 | user.AddUserCmd.MarkFlagRequired("uid") 58 | user.AddUserCmd.MarkFlagRequired("userPassword") 59 | 60 | // update user, Only information other than the user's uid can be updated. If you want to update the user's uid, you need to use the UpdateUserDN method. 61 | userCmd.AddCommand(user.UpdateUserCmd) 62 | bset := user.UpdateUserCmd.Flags() 63 | bset.StringP("uid", "u", "", "user uid, Uid is the unique ID of the user when updating user information. uid cannot be updated directly here.") 64 | bset.StringP("cn", "c", "", "user cn, It is usually a user name, adjusted according to the actual situation of your own use.") 65 | bset.StringP("sn", "s", "", "user sn, Usually a surname, adjusted according to the actual situation of your own use") 66 | bset.StringP("businessCategory", "b", "", "user businessCategory") 67 | bset.StringP("departmentNumber", "d", "", "user departmentNumber") 68 | bset.StringP("description", "", "", "user description") 69 | bset.StringP("displayName", "", "", "user displayName") 70 | bset.StringP("mail", "m", "", "user mail") 71 | bset.StringP("employeeNumber", "e", "", "user employeeNumber") 72 | bset.StringP("givenName", "g", "", "user givenName") 73 | bset.StringP("postalAddress", "a", "", "user postalAddress") 74 | bset.StringP("mobile", "", "", "user mobile") 75 | user.UpdateUserCmd.MarkFlagRequired("uid") 76 | 77 | // get user by uid 78 | userCmd.AddCommand(user.GetUserByUIDCmd) 79 | cset := user.GetUserByUIDCmd.Flags() 80 | cset.StringP("uid", "u", "", "user uid") 81 | user.GetUserByUIDCmd.MarkFlagRequired("uid") 82 | 83 | // delete user by uid 84 | userCmd.AddCommand(user.DelUserByUIDCmd) 85 | dset := user.DelUserByUIDCmd.Flags() 86 | dset.StringP("uid", "u", "", "user uid") 87 | user.DelUserByUIDCmd.MarkFlagRequired("uid") 88 | 89 | // Check whether the user password is correct 90 | userCmd.AddCommand(user.CheckUserPassCmd) 91 | eset := user.CheckUserPassCmd.Flags() 92 | eset.StringP("uid", "u", "", "user uid") 93 | eset.StringP("pwd", "p", "", "user password") 94 | user.CheckUserPassCmd.MarkFlagRequired("uid") 95 | user.CheckUserPassCmd.MarkFlagRequired("pwd") 96 | 97 | // update user uid 98 | userCmd.AddCommand(user.UpdataUserDNCmd) 99 | fset := user.UpdataUserDNCmd.Flags() 100 | fset.StringP("olduid", "o", "", "user old uid") 101 | fset.StringP("newuid", "n", "", "user new uid") 102 | user.UpdataUserDNCmd.MarkFlagRequired("olduid") 103 | user.UpdataUserDNCmd.MarkFlagRequired("newuid") 104 | 105 | // change user password 106 | userCmd.AddCommand(user.ChangeUserPwdCmd) 107 | gset := user.ChangeUserPwdCmd.Flags() 108 | gset.StringP("uid", "u", "", "user uid") 109 | gset.StringP("oldpwd", "o", "", "user old password") 110 | gset.StringP("newpwd", "n", "", "user new password") 111 | user.ChangeUserPwdCmd.MarkFlagRequired("uid") 112 | 113 | } 114 | -------------------------------------------------------------------------------- /docs/start-ldap-eryajf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | SERVICE=ldap-service-eryajf 4 | HOST_NAME=ldap-server-eryajf 5 | LDAP_DOMAIN=eryajf.net 6 | LDAP_DC=eryajf 7 | LDAP_DC_ORG=net 8 | NETWORK_ADAPTER=eth0 9 | PASSWORD=123465 10 | OPENLDAP="1.5.0" 11 | PHPLDAPADMIN="0.9.0" 12 | HTTPS_PORT=8089 13 | OPENLDAP_PORT=390 14 | 15 | docker run \ 16 | -p ${OPENLDAP_PORT}:389 \ 17 | --name ${SERVICE} \ 18 | --hostname ${HOST_NAME} \ 19 | --env LDAP_ORGANISATION="Eyajf-Group" \ 20 | --env LDAP_DOMAIN=${LDAP_DOMAIN} \ 21 | --env LDAP_ADMIN_PASSWORD=${PASSWORD} \ 22 | --detach osixia/openldap:${OPENLDAP} 23 | 24 | docker run \ 25 | -p ${HTTPS_PORT}:80 \ 26 | --name ${SERVICE}-admin \ 27 | --hostname ${HOST_NAME}-admin \ 28 | --link ${SERVICE}:${HOST_NAME} \ 29 | --env PHPLDAPADMIN_LDAP_HOSTS=${HOST_NAME} \ 30 | --env PHPLDAPADMIN_HTTPS=false \ 31 | --detach \ 32 | osixia/phpldapadmin:${PHPLDAPADMIN} 33 | 34 | sleep 1 35 | echo "-----------------------------------" 36 | PHPLDAP_IP=$(docker inspect -f "{{ .NetworkSettings.IPAddress }}" ${SERVICE}) 37 | docker exec ${SERVICE} ldapsearch -x -H ldap://${PHPLDAP_IP}:389 -b "dc=${LDAP_DC},dc=${LDAP_DC_ORG}" -D "cn=admin,dc=${LDAP_DC},dc=${LDAP_DC_ORG}" -w ${PASSWORD} 38 | echo "-----------------------------------" 39 | # If it is not debugged locally, replace it with host IP here. 40 | PUB_IP="localhost" 41 | echo "Go to: https://${PUB_IP}:${HTTPS_PORT}" 42 | echo "Login DN: cn=admin,dc=${LDAP_DC},dc=${LDAP_DC_ORG}" 43 | echo "Password: ${PASSWORD}" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eryajf/ldapctl 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/eryajf/ldapool v0.0.0-20221013074353-72a0f4271060 7 | github.com/go-ldap/ldap/v3 v3.4.4 8 | github.com/liushuochen/gotable v0.0.0-20220428143135-462ba13ad2aa 9 | github.com/spf13/cobra v1.4.0 10 | ) 11 | 12 | require ( 13 | github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect 14 | github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect 15 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 16 | github.com/spf13/pflag v1.0.5 // indirect 17 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= 2 | github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 4 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/eryajf/ldapool v0.0.0-20221013074353-72a0f4271060 h1:bBj1VoOw00okVhsPZoFa6vraT+TB1797F9VMiluRQL4= 7 | github.com/eryajf/ldapool v0.0.0-20221013074353-72a0f4271060/go.mod h1:YnkkbWywJ5OzcOP2cR1ZvUmKl24O6RLqA3K1heaoyq0= 8 | github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= 9 | github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= 10 | github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= 11 | github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= 12 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 13 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 14 | github.com/liushuochen/gotable v0.0.0-20220428143135-462ba13ad2aa h1:H2JaLkZR0cSO1gnWqggiF7GGrk3XD9DGmNKxykZO08s= 15 | github.com/liushuochen/gotable v0.0.0-20220428143135-462ba13ad2aa/go.mod h1:CxUy8nDvutaC1pOfaG9TRoYwdHHqoNstSPPKhomC9k8= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 19 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 20 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 21 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 22 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 25 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 26 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 27 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 28 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 29 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 33 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 34 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 36 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 38 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 39 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 eryajf 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "github.com/eryajf/ldapctl/cmd" 20 | "github.com/eryajf/ldapctl/public" 21 | ) 22 | 23 | func main() { 24 | cmd.Execute() 25 | defer public.InitCli().Close() 26 | } 27 | -------------------------------------------------------------------------------- /public/group.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "fmt" 5 | 6 | ldap "github.com/go-ldap/ldap/v3" 7 | ) 8 | 9 | // Group struct, The usage of some fields is defined according to the actual situation. 10 | type Group struct { 11 | CN string `json:"cn"` // 是组的拼音名称 12 | Desc string `json:"desc"` // 是组的描述,换句话说就是组的中文叫法 13 | Member []string `json:"member"` // 是组的成员 14 | ObjClass []string `json:"objClass"` // 是组的类型 15 | } 16 | 17 | func GetAllGroup() (groups []*Group, err error) { 18 | // Construct query request 19 | searchRequest := ldap.NewSearchRequest( 20 | LDAP_GROUP_DN, // This is basedn, we will start searching from this node. 21 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly 22 | "(&(objectClass=groupofnames))", // This is Filter for LDAP query 23 | []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned 24 | nil, 25 | ) 26 | var sr *ldap.SearchResult 27 | // Search through ldap built-in search 28 | sr, err = InitCli().Search(searchRequest) 29 | if err != nil { 30 | return nil, err 31 | } 32 | // Refers to the entry that returns data. If it is greater than 0, the interface returns normally. 33 | if len(sr.Entries) > 0 { 34 | for _, v := range sr.Entries { 35 | groups = append(groups, &Group{ 36 | CN: v.GetAttributeValue("cn"), 37 | Desc: v.GetAttributeValue("description"), 38 | Member: v.GetAttributeValues("member"), 39 | ObjClass: v.GetAttributeValues("objectClass"), 40 | }) 41 | } 42 | } 43 | return 44 | } 45 | 46 | func GetGroupMenber(group string) (menbers []string, err error) { 47 | // Construct query request 48 | searchRequest := ldap.NewSearchRequest( 49 | LDAP_GROUP_DN, // This is basedn, we will start searching from this node. 50 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly 51 | fmt.Sprintf("(&(objectClass=groupofnames)(cn=%s))", group), // This is Filter for LDAP query 52 | []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned 53 | nil, 54 | ) 55 | // fmt.Sprintf("(&(objectClass=groupofnames)(cn=%s))", group) 56 | var sr *ldap.SearchResult 57 | // Search through ldap built-in search 58 | sr, err = InitCli().Search(searchRequest) 59 | if err != nil { 60 | return nil, err 61 | } 62 | // Refers to the entry that returns data. If it is greater than 0, the interface returns normally. 63 | if len(sr.Entries) > 0 { 64 | menbers = sr.Entries[0].GetAttributeValues("member") 65 | } 66 | return 67 | } 68 | 69 | // AddGroup 70 | func AddGroup(group Group) error { 71 | add := ldap.NewAddRequest(GetGroupDN(group.CN), nil) 72 | 73 | add.Attribute("objectClass", []string{"groupOfNames", "top"}) // If groupOfNAmes is defined, member must be specified, otherwise the error is reported as follows:object class 'groupOfNames' requires attribute 'member' 74 | add.Attribute("cn", []string{group.CN}) 75 | add.Attribute("description", []string{group.Desc}) 76 | add.Attribute("member", []string{LDAP_ADMIN_DN}) // Therefore, when creating a group here, admin is added to it by default, so as not to report the above error without personnel during creation. 77 | 78 | return InitCli().Add(add) 79 | } 80 | 81 | // UpdateGroup 82 | func UpdateGroup(group Group) error { 83 | modify := ldap.NewModifyRequest(GetGroupDN(group.CN), nil) 84 | modify.Replace("description", []string{group.Desc}) 85 | return InitCli().Modify(modify) 86 | } 87 | 88 | // DelGroup 89 | func DelGroup(group string) error { 90 | del := ldap.NewDelRequest(GetGroupDN(group), nil) 91 | return InitCli().Del(del) 92 | } 93 | 94 | // AddUserToGroup 95 | func AddUserToGroup(user, group string) error { 96 | addUserToGroup := ldap.NewModifyRequest(GetGroupDN(group), nil) 97 | addUserToGroup.Add("member", []string{GetUserDN(user)}) 98 | return InitCli().Modify(addUserToGroup) 99 | } 100 | 101 | // RemoveUserFromGroup 102 | func RemoveUserFromGroup(user, group string) error { 103 | removeUserFromGroup := ldap.NewModifyRequest(GetGroupDN(group), nil) 104 | removeUserFromGroup.Delete("member", []string{GetUserDN(user)}) 105 | return InitCli().Modify(removeUserFromGroup) 106 | } 107 | -------------------------------------------------------------------------------- /public/public.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eryajf/ldapool" 7 | ldap "github.com/go-ldap/ldap/v3" 8 | ) 9 | 10 | // ========= test-ldap ============= 11 | const ( 12 | LDAP_URL = "ldap://localhost:389" // eryajf 13 | LDAP_BASE_DN = "dc=eryajf,dc=net" 14 | LDAP_ADMIN_DN = "cn=admin,dc=eryajf,dc=net" 15 | LDAP_ADMIN_PASS = "123456" 16 | LDAP_USER_DN = "ou=People,dc=eryajf,dc=net" 17 | LDAP_GROUP_DN = "ou=Group,dc=eryajf,dc=net" 18 | ) 19 | 20 | // Init ldap conn 21 | func InitCli() (l *ldap.Conn) { 22 | conn, err := ldapool.Open(ldapool.LdapConfig{ 23 | Url: LDAP_URL, 24 | BaseDN: LDAP_BASE_DN, 25 | AdminDN: LDAP_ADMIN_DN, 26 | AdminPass: LDAP_ADMIN_PASS, 27 | MaxOpen: 30, 28 | DsName: "", 29 | }) 30 | if err != nil { 31 | panic(fmt.Sprintf("get conn failed:%v\n", err)) 32 | } 33 | 34 | return conn 35 | } 36 | 37 | // GetGroupDN 38 | func GetGroupDN(group string) (dn string) { 39 | return fmt.Sprintf("cn=%s,%s", group, LDAP_GROUP_DN) // Example: cn=group,ou=Group,dc=eryajf,dc=net 40 | } 41 | 42 | // GetUserDN 43 | func GetUserDN(user string) (dn string) { 44 | return fmt.Sprintf("uid=%s,%s", user, LDAP_USER_DN) // Example: uid=user,ou=People,dc=eryajf,dc=net 45 | } 46 | -------------------------------------------------------------------------------- /public/user.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "fmt" 5 | 6 | ldap "github.com/go-ldap/ldap/v3" 7 | ) 8 | 9 | // User is a struct for user,The usage of some fields is defined according to the actual situation. 10 | type User struct { 11 | CN string `json:"cn"` // 中文名全内容 12 | SN string `json:"sn"` // 名字拼音,可能是缩减的 13 | BusinessCategory string `json:"businessCategory"` // 业务类别,部门名字 14 | DepartmentNumber string `json:"departmentNumber"` // 部门编号,此处可以存放员工的职位 15 | Description string `json:"description"` // 描述 16 | DisplayName string `json:"displayName"` // 展示名字,可以是中文名字 17 | Mail string `json:"mail"` // 邮箱 18 | EmployeeNumber string `json:"employeeNumber"` // 员工工号 19 | GivenName string `json:"givenName"` // 给定名字,如果公司有花名,可以用这个字段 20 | PostalAddress string `json:"postalAddress"` // 家庭住址 21 | Mobile string `json:"mobile"` // 手机号 22 | UID string `json:"uid"` // 用户名 23 | Password string `json:"password"` // 密码 24 | } 25 | 26 | // AddUser add a user 27 | func AddUser(user User) error { 28 | add := ldap.NewAddRequest(GetUserDN(user.UID), nil) 29 | 30 | add.Attribute("objectClass", []string{"inetOrgPerson"}) 31 | add.Attribute("cn", []string{user.CN}) 32 | add.Attribute("sn", []string{user.SN}) 33 | add.Attribute("businessCategory", []string{user.BusinessCategory}) 34 | add.Attribute("departmentNumber", []string{user.DepartmentNumber}) 35 | add.Attribute("description", []string{user.Description}) 36 | add.Attribute("displayName", []string{user.DisplayName}) 37 | add.Attribute("mail", []string{user.Mail}) 38 | add.Attribute("employeeNumber", []string{user.EmployeeNumber}) 39 | add.Attribute("givenName", []string{user.GivenName}) 40 | add.Attribute("postalAddress", []string{user.PostalAddress}) 41 | add.Attribute("mobile", []string{user.Mobile}) 42 | add.Attribute("uid", []string{user.UID}) 43 | add.Attribute("userPassword", []string{user.Password}) 44 | 45 | return InitCli().Add(add) 46 | } 47 | 48 | // UpdateUser update a user 49 | func UpdateUser(user User) error { 50 | modify := ldap.NewModifyRequest(GetUserDN(user.UID), nil) 51 | 52 | modify.Replace("cn", []string{user.CN}) 53 | modify.Replace("sn", []string{user.SN}) 54 | modify.Replace("businessCategory", []string{user.BusinessCategory}) 55 | modify.Replace("departmentNumber", []string{user.DepartmentNumber}) 56 | modify.Replace("description", []string{user.Description}) 57 | modify.Replace("displayName", []string{user.DisplayName}) 58 | modify.Replace("mail", []string{user.Mail}) 59 | modify.Replace("employeeNumber", []string{user.EmployeeNumber}) 60 | modify.Replace("givenName", []string{user.GivenName}) 61 | modify.Replace("postalAddress", []string{user.PostalAddress}) 62 | modify.Replace("mobile", []string{user.Mobile}) 63 | modify.Replace("uid", []string{user.UID}) 64 | modify.Replace("userPassword", []string{user.Password}) 65 | 66 | return InitCli().Modify(modify) 67 | } 68 | 69 | func GetAllUser() (users []*User, err error) { 70 | // Construct query request 71 | searchRequest := ldap.NewSearchRequest( 72 | LDAP_USER_DN, // This is basedn, we will start searching from this node. 73 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly 74 | "(&(objectClass=organizationalPerson))", // This is Filter for LDAP query 75 | []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned 76 | nil, 77 | ) 78 | var sr *ldap.SearchResult 79 | // Search through ldap built-in search 80 | sr, err = InitCli().Search(searchRequest) 81 | if err != nil { 82 | return nil, err 83 | } 84 | // Refers to the entry that returns data. If it is greater than 0, the interface returns normally. 85 | if len(sr.Entries) > 0 { 86 | for _, user := range sr.Entries { 87 | users = append(users, &User{ 88 | CN: user.GetAttributeValue("cn"), 89 | SN: user.GetAttributeValue("sn"), 90 | BusinessCategory: user.GetAttributeValue("businessCategory"), 91 | DepartmentNumber: user.GetAttributeValue("departmentNumber"), 92 | Description: user.GetAttributeValue("description"), 93 | DisplayName: user.GetAttributeValue("displayName"), 94 | Mail: user.GetAttributeValue("mail"), 95 | EmployeeNumber: user.GetAttributeValue("employeeNumber"), 96 | GivenName: user.GetAttributeValue("givenName"), 97 | PostalAddress: user.GetAttributeValue("postalAddress"), 98 | Mobile: user.GetAttributeValue("mobile"), 99 | UID: user.GetAttributeValue("uid"), 100 | Password: "******", 101 | }) 102 | } 103 | } 104 | return 105 | } 106 | 107 | func GetUserByUID(uid string) (rst *User, err error) { 108 | // Construct query request 109 | searchRequest := ldap.NewSearchRequest( 110 | LDAP_USER_DN, // This is basedn, we will start searching from this node. 111 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly 112 | fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", uid), // The request to filter user queries is defined here, and other filtering parameters can be changed. 113 | []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned 114 | nil, 115 | ) 116 | var sr *ldap.SearchResult 117 | // Search through ldap built-in search 118 | sr, err = InitCli().Search(searchRequest) 119 | if err != nil { 120 | return nil, err 121 | } 122 | // Refers to the entry that returns data. If it is greater than 0, the interface returns normally. 123 | if len(sr.Entries) > 0 { 124 | user := sr.Entries[0] 125 | rst = &User{ 126 | CN: user.GetAttributeValue("cn"), 127 | SN: user.GetAttributeValue("sn"), 128 | BusinessCategory: user.GetAttributeValue("businessCategory"), 129 | DepartmentNumber: user.GetAttributeValue("departmentNumber"), 130 | Description: user.GetAttributeValue("description"), 131 | DisplayName: user.GetAttributeValue("displayName"), 132 | Mail: user.GetAttributeValue("mail"), 133 | EmployeeNumber: user.GetAttributeValue("employeeNumber"), 134 | GivenName: user.GetAttributeValue("givenName"), 135 | PostalAddress: user.GetAttributeValue("postalAddress"), 136 | Mobile: user.GetAttributeValue("mobile"), 137 | UID: user.GetAttributeValue("uid"), 138 | Password: "******", 139 | } 140 | } 141 | return rst, nil 142 | } 143 | 144 | // DelUser delete a user 145 | func DelUser(uid string) error { 146 | del := ldap.NewDelRequest(GetUserDN(uid), nil) 147 | return InitCli().Del(del) 148 | } 149 | 150 | // CheckUser Based on the user name and password, it is detected whether the user is normally available. 151 | // Users often say that they have failed to log in to a certain system. This method can be used to check what is wrong. 152 | func CheckUser(username, password string) error { 153 | udn := GetUserDN(username) 154 | if username == "admin" { 155 | udn = LDAP_ADMIN_DN 156 | } 157 | return InitCli().Bind(udn, password) 158 | } 159 | 160 | // see https://github.com/go-ldap/ldap/pull/54 161 | // UpdateUserDN update user DN 162 | func UpdateUserDN(olduid, newuid string) error { 163 | modify := ldap.NewModifyDNRequest( 164 | GetUserDN(olduid), 165 | fmt.Sprintf("uid=%s", newuid), 166 | true, 167 | "") 168 | return InitCli().ModifyDN(modify) 169 | } 170 | 171 | // ModifyUserPassword 172 | // User uid, old password, and new password need to be passed. 173 | // The old password is not a mandatory item. If it is left blank, the program will overwrite the old password and return it. Usually this should be sent to the modified students by mail. 174 | func ModifyUserPassword(uid, oldpasswd, newpasswd string) (string, error) { 175 | passModify := ldap.NewPasswordModifyRequest( 176 | GetUserDN(uid), 177 | oldpasswd, 178 | newpasswd) 179 | result, err := InitCli().PasswordModify(passModify) 180 | if err != nil { 181 | return "", err 182 | } 183 | return result.GeneratedPassword, nil 184 | } 185 | --------------------------------------------------------------------------------