ldap-cli 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- .kiro/specs/ldap-cli/design.md +386 -0
- .kiro/specs/ldap-cli/requirements.md +123 -0
- .kiro/specs/ldap-cli/tasks.md +246 -0
- CHANGELOG.md +47 -0
- README.md +145 -0
- docs/reference.md +198 -0
- ldap_cli-0.2.0.dist-info/METADATA +176 -0
- ldap_cli-0.2.0.dist-info/RECORD +18 -0
- ldap_cli-0.2.0.dist-info/WHEEL +4 -0
- ldap_cli-0.2.0.dist-info/entry_points.txt +2 -0
- ldap_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
- ldapc/__init__.py +7 -0
- ldapc/_version.py +23 -0
- ldapc/cli.py +490 -0
- ldapc/config.py +184 -0
- ldapc/exceptions.py +37 -0
- ldapc/formatter.py +282 -0
- ldapc/ldap_client.py +506 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Implementation Plan: ldap-cli
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Implement `ldapc`, a Python CLI tool for querying LDAP directories. The implementation follows the workspace conventions (hatchling build, module-per-command, pytest testing) and builds incrementally: project scaffolding → config layer → LDAP client → formatter → CLI wiring → integration tests.
|
|
6
|
+
|
|
7
|
+
## Tasks
|
|
8
|
+
|
|
9
|
+
- [x] 1. Set up project structure and scaffolding
|
|
10
|
+
- [x] 1.1 Create project directory and `pyproject.toml`
|
|
11
|
+
- Create `ldapc/` project root directory
|
|
12
|
+
- Create `pyproject.toml` with hatchling build backend, project metadata, dependencies (`ldap3`, `keyring`, `pyyaml`), dev dependencies (`pytest`, `pytest-cov`, `hypothesis`, `ruff`, `mypy`, `bandit`), and `[project.scripts]` entry for `ldapc = "ldapc.cli:main"`
|
|
13
|
+
- Create `ldapc/ldapc/__init__.py` with package-level imports
|
|
14
|
+
- Create `ldapc/ldapc/_version.py` with version extraction from CHANGELOG.md
|
|
15
|
+
- Create `ldapc/CHANGELOG.md` with initial `[Unreleased]` section and `[0.1.0]` entry
|
|
16
|
+
- Create `ldapc/README.md` with project description, installation, and usage
|
|
17
|
+
- Create `ldapc/LICENSE` with MIT license
|
|
18
|
+
- Create `ldapc/docs/README.md` as documentation index
|
|
19
|
+
- Create `ldapc/.gitignore` for Python projects
|
|
20
|
+
- _Requirements: 1.1, 1.3_
|
|
21
|
+
|
|
22
|
+
- [x] 1.2 Create exceptions module
|
|
23
|
+
- Create `ldapc/ldapc/exceptions.py` with `LdapcError`, `ConfigError`, `ConnectionError`, and `AuthenticationError` exception classes
|
|
24
|
+
- All exceptions inherit from `LdapcError`
|
|
25
|
+
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
|
|
26
|
+
|
|
27
|
+
- [x] 1.3 Create test scaffolding
|
|
28
|
+
- Create `ldapc/tests/__init__.py`
|
|
29
|
+
- Create `ldapc/tests/conftest.py` with shared fixtures (tmp config directory, mock keyring, mock LDAP server)
|
|
30
|
+
- Create `ldapc/pytest.ini` with coverage settings, smoke marker registration, and minimum 80% coverage threshold
|
|
31
|
+
- _Requirements: (testing infrastructure)_
|
|
32
|
+
|
|
33
|
+
- [x] 2. Implement configuration module
|
|
34
|
+
- [x] 2.1 Implement `LdapcConfig` dataclass and YAML serialization
|
|
35
|
+
- Create `ldapc/ldapc/config.py` with `LdapcConfig` dataclass (host, username, base_dn)
|
|
36
|
+
- Implement `config_to_yaml()` to serialize config to YAML string
|
|
37
|
+
- Implement `yaml_to_config()` to parse YAML string into config object, raising `ConfigError` on invalid input
|
|
38
|
+
- Implement `load_config()` to read from `~/.ldapc/config.yaml`, raising `ConfigError` if missing or invalid
|
|
39
|
+
- Implement `save_config()` to write config with 0600 permissions, creating `~/.ldapc/` directory if needed
|
|
40
|
+
- All functions must have type hints and Google-style docstrings
|
|
41
|
+
- _Requirements: 2.1, 2.2, 2.4, 2.6, 2.7, 3.1, 3.2, 3.3, 3.5_
|
|
42
|
+
|
|
43
|
+
- [x] 2.2 Write property test: Configuration round-trip (Property 1)
|
|
44
|
+
- **Property 1: Configuration round-trip**
|
|
45
|
+
- Generate random `LdapcConfig` objects with hypothesis `@st.composite`
|
|
46
|
+
- Assert `yaml_to_config(config_to_yaml(config))` produces equivalent object
|
|
47
|
+
- Tag: `# Feature: ldap-cli, Property 1: Configuration round-trip`
|
|
48
|
+
- Create `ldapc/tests/test_config_properties.py`
|
|
49
|
+
- **Validates: Requirements 2.4, 3.1, 3.3, 3.4**
|
|
50
|
+
|
|
51
|
+
- [x] 2.3 Write property test: Invalid YAML produces descriptive error (Property 2)
|
|
52
|
+
- **Property 2: Invalid YAML produces a descriptive error**
|
|
53
|
+
- Generate random non-YAML strings and YAML missing required fields
|
|
54
|
+
- Assert `yaml_to_config()` raises `ConfigError` with non-empty message
|
|
55
|
+
- Tag: `# Feature: ldap-cli, Property 2: Invalid YAML produces a descriptive error`
|
|
56
|
+
- Add to `ldapc/tests/test_config_properties.py`
|
|
57
|
+
- **Validates: Requirements 3.2**
|
|
58
|
+
|
|
59
|
+
- [x] 2.4 Implement keyring credential storage
|
|
60
|
+
- Implement `store_password(username, password)` using `keyring.set_password` with service name `ldapc`
|
|
61
|
+
- Implement `retrieve_password(username)` using `keyring.get_password`, raising `ConfigError` if not found
|
|
62
|
+
- _Requirements: 4.1, 4.2, 4.3, 4.4_
|
|
63
|
+
|
|
64
|
+
- [x] 2.5 Write property test: Password never written to disk (Property 3)
|
|
65
|
+
- **Property 3: Password is never written to disk**
|
|
66
|
+
- Generate random passwords, run `save_config`, assert password does not appear in any file under `~/.ldapc/`
|
|
67
|
+
- Tag: `# Feature: ldap-cli, Property 3: Password is never written to disk`
|
|
68
|
+
- Add to `ldapc/tests/test_config_properties.py`
|
|
69
|
+
- **Validates: Requirements 4.4**
|
|
70
|
+
|
|
71
|
+
- [x] 2.6 Write unit tests for configuration module
|
|
72
|
+
- Create `ldapc/tests/test_configure.py`
|
|
73
|
+
- Test: load_config with valid file, missing file, invalid YAML
|
|
74
|
+
- Test: save_config creates directory, sets permissions
|
|
75
|
+
- Test: store_password and retrieve_password with mocked keyring
|
|
76
|
+
- Test: ConfigError raised when keychain has no stored password
|
|
77
|
+
- _Requirements: 2.1, 2.2, 2.4, 2.6, 2.7, 3.1, 3.2, 3.5, 4.1, 4.2, 4.3_
|
|
78
|
+
|
|
79
|
+
- [x] 3. Checkpoint - Ensure configuration tests pass
|
|
80
|
+
- Ensure all tests pass, ask the user if questions arise.
|
|
81
|
+
|
|
82
|
+
- [x] 4. Implement LDAP client module
|
|
83
|
+
- [x] 4.1 Implement `LdapClient` class with connection management
|
|
84
|
+
- Create `ldapc/ldapc/ldap_client.py` with `LdapEntry` dataclass and `LdapClient` class
|
|
85
|
+
- Implement `__init__` to establish TLS connection with 10-second timeout
|
|
86
|
+
- Implement `search_users(search_term, base_dn)` to search by cn and uid
|
|
87
|
+
- Implement `search_groups(search_term, base_dn)` to search by cn
|
|
88
|
+
- Implement `close()` to unbind and close connection
|
|
89
|
+
- Handle connection errors, timeouts, TLS failures, and auth errors by raising appropriate exceptions
|
|
90
|
+
- Use context manager pattern (`__enter__`/`__exit__`) for automatic cleanup
|
|
91
|
+
- _Requirements: 5.1, 5.3, 5.4, 5.5, 6.1, 6.3, 6.4, 6.5, 8.1, 8.2, 8.3, 8.4_
|
|
92
|
+
|
|
93
|
+
- [x] 4.2 Write unit tests for LDAP client
|
|
94
|
+
- Create `ldapc/tests/test_ldap_client.py`
|
|
95
|
+
- Test: successful user search returns LdapEntry list (mocked ldap3)
|
|
96
|
+
- Test: successful group search returns LdapEntry list (mocked ldap3)
|
|
97
|
+
- Test: connection timeout raises ConnectionError
|
|
98
|
+
- Test: TLS failure raises ConnectionError
|
|
99
|
+
- Test: invalid credentials raises AuthenticationError
|
|
100
|
+
- Test: close() unbinds connection
|
|
101
|
+
- _Requirements: 5.1, 5.3, 5.4, 5.5, 6.1, 6.3, 6.4, 6.5, 8.1, 8.2, 8.3, 8.4_
|
|
102
|
+
|
|
103
|
+
- [x] 5. Implement formatter module
|
|
104
|
+
- [x] 5.1 Implement search filter builders and output formatters
|
|
105
|
+
- Create `ldapc/ldapc/formatter.py`
|
|
106
|
+
- Implement `build_user_filter(search_term)` to construct LDAP filter matching cn and uid
|
|
107
|
+
- Implement `build_group_filter(search_term)` to construct LDAP filter matching cn
|
|
108
|
+
- Implement `format_user_entries(entries)` for aligned key-value text output with delimiters between entries
|
|
109
|
+
- Implement `format_group_entries(entries)` for aligned key-value text output with delimiters between entries
|
|
110
|
+
- Implement `format_entries_json(entries)` for valid JSON output
|
|
111
|
+
- _Requirements: 5.1, 5.2, 6.1, 6.2, 7.1, 7.2, 7.3_
|
|
112
|
+
|
|
113
|
+
- [x] 5.2 Write property test: User search filter construction (Property 4)
|
|
114
|
+
- **Property 4: User search filter construction**
|
|
115
|
+
- Generate random non-empty search term strings
|
|
116
|
+
- Assert `build_user_filter(term)` produces filter containing both `cn` and `uid` references and the search term
|
|
117
|
+
- Tag: `# Feature: ldap-cli, Property 4: User search filter construction`
|
|
118
|
+
- Create `ldapc/tests/test_search_properties.py`
|
|
119
|
+
- **Validates: Requirements 5.1**
|
|
120
|
+
|
|
121
|
+
- [x] 5.3 Write property test: Group search filter construction (Property 5)
|
|
122
|
+
- **Property 5: Group search filter construction**
|
|
123
|
+
- Generate random non-empty search term strings
|
|
124
|
+
- Assert `build_group_filter(term)` produces filter containing `cn` reference and the search term
|
|
125
|
+
- Tag: `# Feature: ldap-cli, Property 5: Group search filter construction`
|
|
126
|
+
- Add to `ldapc/tests/test_search_properties.py`
|
|
127
|
+
- **Validates: Requirements 6.1**
|
|
128
|
+
|
|
129
|
+
- [x] 5.4 Write property test: Entry formatting contains all required fields (Property 6)
|
|
130
|
+
- **Property 6: Entry formatting contains all required fields**
|
|
131
|
+
- Generate random `LdapEntry` objects with required attributes
|
|
132
|
+
- Assert formatted output contains DN and all attribute values
|
|
133
|
+
- Tag: `# Feature: ldap-cli, Property 6: Entry formatting contains all required fields`
|
|
134
|
+
- Create `ldapc/tests/test_formatter_properties.py`
|
|
135
|
+
- **Validates: Requirements 5.2, 6.2, 7.1**
|
|
136
|
+
|
|
137
|
+
- [x] 5.5 Write property test: JSON output is valid and round-trips (Property 7)
|
|
138
|
+
- **Property 7: JSON output is valid and round-trips**
|
|
139
|
+
- Generate random lists of `LdapEntry` objects
|
|
140
|
+
- Assert `format_entries_json(entries)` produces valid JSON that deserializes to equivalent data
|
|
141
|
+
- Tag: `# Feature: ldap-cli, Property 7: JSON output is valid and round-trips`
|
|
142
|
+
- Add to `ldapc/tests/test_formatter_properties.py`
|
|
143
|
+
- **Validates: Requirements 7.3**
|
|
144
|
+
|
|
145
|
+
- [x] 5.6 Write unit tests for formatter module
|
|
146
|
+
- Create `ldapc/tests/test_formatter.py`
|
|
147
|
+
- Test: format_user_entries with single and multiple entries
|
|
148
|
+
- Test: format_group_entries with single and multiple entries
|
|
149
|
+
- Test: format_entries_json produces valid JSON
|
|
150
|
+
- Test: multiple entries separated by delimiter
|
|
151
|
+
- _Requirements: 5.2, 6.2, 7.1, 7.2, 7.3_
|
|
152
|
+
|
|
153
|
+
- [x] 6. Checkpoint - Ensure all module tests pass
|
|
154
|
+
- Ensure all tests pass, ask the user if questions arise.
|
|
155
|
+
|
|
156
|
+
- [x] 7. Implement CLI entry point and command wiring
|
|
157
|
+
- [x] 7.1 Implement argument parser and main entry point
|
|
158
|
+
- Create `ldapc/ldapc/cli.py` with `build_parser()` and `main()` functions
|
|
159
|
+
- Register `--configure`, `--user`, `--group`, `--version`, `--help`, `--json` flags
|
|
160
|
+
- Display help when invoked without arguments
|
|
161
|
+
- Map exception types to exit codes in `main()`: `ConfigError` → 2, `ConnectionError` → 1, `AuthenticationError` → 1
|
|
162
|
+
- Write errors to stderr, results to stdout
|
|
163
|
+
- _Requirements: 1.1, 1.2, 1.3, 1.4, 7.4, 9.1, 9.2, 9.3, 9.4, 9.5_
|
|
164
|
+
|
|
165
|
+
- [x] 7.2 Implement configure command flow
|
|
166
|
+
- In `cli.py`, implement the `--configure` handler that prompts for host, username, and password
|
|
167
|
+
- Use `getpass.getpass()` for password input
|
|
168
|
+
- Call `save_config()` and `store_password()` to persist settings
|
|
169
|
+
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7_
|
|
170
|
+
|
|
171
|
+
- [x] 7.3 Implement user search command flow
|
|
172
|
+
- In `cli.py`, implement the `--user` handler
|
|
173
|
+
- Load config, retrieve password from keychain, create LdapClient, execute search, format and print results
|
|
174
|
+
- Handle no-results case with informative message
|
|
175
|
+
- Close LDAP connection in `finally` block
|
|
176
|
+
- Support `--json` flag for JSON output
|
|
177
|
+
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 7.3, 8.2_
|
|
178
|
+
|
|
179
|
+
- [x] 7.4 Implement group search command flow
|
|
180
|
+
- In `cli.py`, implement the `--group` handler
|
|
181
|
+
- Load config, retrieve password from keychain, create LdapClient, execute search, format and print results
|
|
182
|
+
- Handle no-results case with informative message
|
|
183
|
+
- Close LDAP connection in `finally` block
|
|
184
|
+
- Support `--json` flag for JSON output
|
|
185
|
+
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 7.3, 8.2_
|
|
186
|
+
|
|
187
|
+
- [x] 7.5 Write unit tests for CLI module
|
|
188
|
+
- Create `ldapc/tests/test_cli.py`
|
|
189
|
+
- Test: `--help` displays help text and exits 0
|
|
190
|
+
- Test: `--version` displays version string and exits 0
|
|
191
|
+
- Test: no arguments displays help
|
|
192
|
+
- Test: `--user` with mocked LDAP returns formatted results
|
|
193
|
+
- Test: `--group` with mocked LDAP returns formatted results
|
|
194
|
+
- Test: `--json` flag produces JSON output
|
|
195
|
+
- Test: connection error exits with code 1
|
|
196
|
+
- Test: config error exits with code 2
|
|
197
|
+
- Test: errors written to stderr, results to stdout
|
|
198
|
+
- _Requirements: 1.1, 1.2, 1.3, 1.4, 7.4, 9.1, 9.2, 9.3, 9.4, 9.5_
|
|
199
|
+
|
|
200
|
+
- [x] 7.6 Write smoke tests
|
|
201
|
+
- Create `ldapc/tests/test_smoke.py`
|
|
202
|
+
- Mark all tests with `@pytest.mark.smoke`
|
|
203
|
+
- Test: `ldapc --help` exits 0
|
|
204
|
+
- Test: `ldapc --version` exits 0
|
|
205
|
+
- Test: `ldapc --user test` without config exits 2
|
|
206
|
+
- _Requirements: 1.1, 1.2, 1.3, 9.4, 9.5_
|
|
207
|
+
|
|
208
|
+
- [x] 8. Checkpoint - Ensure full test suite passes
|
|
209
|
+
- Ensure all tests pass, ask the user if questions arise.
|
|
210
|
+
|
|
211
|
+
- [x] 9. Integration tests and final wiring
|
|
212
|
+
- [x] 9.1 Write integration tests for keyring flow
|
|
213
|
+
- Create `ldapc/tests/test_keyring_integration.py`
|
|
214
|
+
- Test: full configure → store → retrieve password flow with mocked keyring backend
|
|
215
|
+
- Test: retrieve fails gracefully when no password stored
|
|
216
|
+
- _Requirements: 4.1, 4.2, 4.3_
|
|
217
|
+
|
|
218
|
+
- [x] 9.2 Write integration tests for LDAP connection lifecycle
|
|
219
|
+
- Create `ldapc/tests/test_ldap_integration.py`
|
|
220
|
+
- Test: connect → search → unbind lifecycle with mocked ldap3 server
|
|
221
|
+
- Test: connection is always closed even on error (finally block)
|
|
222
|
+
- _Requirements: 8.1, 8.2, 8.3, 8.4_
|
|
223
|
+
|
|
224
|
+
- [x] 9.3 Write exit code tests
|
|
225
|
+
- Create `ldapc/tests/test_exit_codes.py`
|
|
226
|
+
- Test: successful query exits 0
|
|
227
|
+
- Test: no results exits 0
|
|
228
|
+
- Test: connection error exits 1
|
|
229
|
+
- Test: auth error exits 1
|
|
230
|
+
- Test: config error exits 2
|
|
231
|
+
- Test: invalid argument exits 2
|
|
232
|
+
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
|
|
233
|
+
|
|
234
|
+
- [x] 10. Final checkpoint - Ensure all tests pass
|
|
235
|
+
- Ensure all tests pass, ask the user if questions arise.
|
|
236
|
+
|
|
237
|
+
## Notes
|
|
238
|
+
|
|
239
|
+
- Tasks marked with `*` are optional and can be skipped for faster MVP
|
|
240
|
+
- Each task references specific requirements for traceability
|
|
241
|
+
- Checkpoints ensure incremental validation
|
|
242
|
+
- Property tests validate universal correctness properties from the design document
|
|
243
|
+
- Unit tests validate specific examples and edge cases
|
|
244
|
+
- The project follows workspace conventions: hatchling build, pytest, hypothesis for PBT
|
|
245
|
+
- Python 3.10+ with type hints on all functions and Google-style docstrings
|
|
246
|
+
- Virtual environment at `/Users/topazb/python/py314/`
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2025-05-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Subcommand-based CLI: `list`, `search`, `get`, `add`, `delete`, `user add group`, `user remove group`
|
|
15
|
+
- `ldapc list users` / `ldapc list groups` to list all entries
|
|
16
|
+
- `ldapc search user|group <term>` for case-insensitive substring search
|
|
17
|
+
- `ldapc get user|group <name>` for exact lookup
|
|
18
|
+
- `ldapc add user|group <name>` to create entries
|
|
19
|
+
- `ldapc delete user|group <name>` to remove entries
|
|
20
|
+
- `ldapc user add group <user> <group>` to add user to group
|
|
21
|
+
- `ldapc user remove group <user> <group>` to remove user from group
|
|
22
|
+
- `--yaml` flag for simplified YAML output (extracts cn from group DNs)
|
|
23
|
+
- `--skip-ssl-verify` can now be set via `LDAPC_SKIP_SSL_VERIFY` environment variable
|
|
24
|
+
- `--debug` flag for verbose logging output to stderr
|
|
25
|
+
- `--docs` flag to browse and view bundled documentation (rich text or markdown, with optional paging)
|
|
26
|
+
- All `.md` files (README, CHANGELOG, docs/) are now included in the wheel
|
|
27
|
+
- `rich` added as a runtime dependency for documentation rendering
|
|
28
|
+
- LDAP filter input escaping (RFC 4515) to prevent filter injection
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- CLI switched from flat flags (`--user`, `--group`) to subcommand structure
|
|
33
|
+
- `--skip-ssl-verify` defaults to the value of `LDAPC_SKIP_SSL_VERIFY` env var (1/true/yes)
|
|
34
|
+
- LDAP search filters now include objectClass constraints for precision
|
|
35
|
+
- User/group container OUs are now configurable via `users_ou` and `groups_ou` in config
|
|
36
|
+
- Project description updated to reflect full management capabilities
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2025-01-01
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Initial project scaffolding
|
|
43
|
+
- CLI entry point (`ldapc`)
|
|
44
|
+
- Configuration module with YAML persistence and keyring credential storage
|
|
45
|
+
- LDAP client module with user and group search
|
|
46
|
+
- Output formatter with text and JSON modes
|
|
47
|
+
- Property-based tests for correctness properties
|
README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# ldapc
|
|
2
|
+
|
|
3
|
+
A Python CLI tool for querying and managing LDAP directories. Provides a simple subcommand interface to list, search, get, add, and delete users and groups, with persistent configuration and secure credential storage via the OS keychain.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- List all users or groups
|
|
8
|
+
- Search users by name/uid, groups by name (case-insensitive substring)
|
|
9
|
+
- Get detailed info on a specific user or group
|
|
10
|
+
- Add and delete users and groups
|
|
11
|
+
- Manage group membership (add/remove users from groups)
|
|
12
|
+
- Text, JSON, and YAML output formats
|
|
13
|
+
- Persistent configuration in `~/.ldapc/config.yaml`
|
|
14
|
+
- Secure credential storage via OS keychain
|
|
15
|
+
- TLS-secured connections with optional certificate verification skip
|
|
16
|
+
- Debug logging for troubleshooting
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# From PyPI
|
|
22
|
+
pip install ldap-cli
|
|
23
|
+
|
|
24
|
+
# From source (editable, with dev deps)
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Configure (host, base DN, bind DN, password)
|
|
32
|
+
ldapc configure
|
|
33
|
+
|
|
34
|
+
# List all users
|
|
35
|
+
ldapc list users
|
|
36
|
+
|
|
37
|
+
# Search for a user
|
|
38
|
+
ldapc search user john
|
|
39
|
+
|
|
40
|
+
# Get user details in YAML
|
|
41
|
+
ldapc get user jdoe --yaml
|
|
42
|
+
|
|
43
|
+
# Skip SSL verification (self-signed certs)
|
|
44
|
+
ldapc list users --skip-ssl-verify
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Configure
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
ldapc configure
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Prompts for LDAP host URL, base DN, bind DN, and password. Config is stored in `~/.ldapc/config.yaml`; password goes to the OS keychain.
|
|
56
|
+
|
|
57
|
+
### List
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ldapc list users # all users
|
|
61
|
+
ldapc list groups # all groups
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Search
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ldapc search user john # users matching "john" (case-insensitive)
|
|
68
|
+
ldapc search group admin # groups matching "admin"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Get
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ldapc get user jdoe # exact user lookup
|
|
75
|
+
ldapc get group devs # exact group lookup
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Add / Delete
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
ldapc add user newuser
|
|
82
|
+
ldapc add group newgroup
|
|
83
|
+
ldapc delete user olduser
|
|
84
|
+
ldapc delete group oldgroup
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Group Membership
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ldapc user add group jdoe developers # add user to group
|
|
91
|
+
ldapc user remove group jdoe developers # remove user from group
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Output Formats
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
ldapc list users --json # JSON output
|
|
98
|
+
ldapc list users --yaml # YAML output (simplified, human-friendly)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Global Flags
|
|
102
|
+
|
|
103
|
+
These flags can appear after any subcommand:
|
|
104
|
+
|
|
105
|
+
| Flag | Env Var | Description |
|
|
106
|
+
|------|---------|-------------|
|
|
107
|
+
| `--debug` | — | Enable debug logging to stderr |
|
|
108
|
+
| `--skip-ssl-verify` | `LDAPC_SKIP_SSL_VERIFY=1` | Skip TLS certificate verification |
|
|
109
|
+
| `--json` | — | Output as JSON |
|
|
110
|
+
| `--yaml` | — | Output as simplified YAML |
|
|
111
|
+
|
|
112
|
+
### Help
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
ldapc --help # top-level help
|
|
116
|
+
ldapc get --help # subcommand help
|
|
117
|
+
ldapc --version # show version
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Documentation
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
ldapc --docs # list available doc files
|
|
124
|
+
ldapc --docs README.md # render README as rich text
|
|
125
|
+
ldapc --docs README.md --page # render with paging
|
|
126
|
+
ldapc --docs README.md --markdown # output raw markdown
|
|
127
|
+
ldapc --docs docs/README.md --page # view docs/README.md with paging
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Running Tests
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pip install -e ".[dev]"
|
|
134
|
+
python3 -m pytest tests/ -v
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
With coverage:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python3 -m pytest tests/ --cov=ldapc --cov-report=term-missing
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT — see [LICENSE](LICENSE) for details.
|
docs/reference.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# ldapc Documentation
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Commands](#commands) — Full command reference
|
|
6
|
+
- [Configuration](#configuration) — Config file and credential storage
|
|
7
|
+
- [Output Formats](#output-formats) — Text, JSON, and YAML
|
|
8
|
+
- [Environment Variables](#environment-variables) — Env var reference
|
|
9
|
+
- [Development](#development) — Contributing, testing, project conventions
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`ldapc` is a Python CLI tool for querying and managing LDAP directories. It uses a subcommand structure for all operations.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### configure
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
ldapc configure
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Interactive setup. Prompts for:
|
|
24
|
+
- **LDAP host URL** — e.g. `ldaps://ldap.example.com:636`
|
|
25
|
+
- **Base DN** — e.g. `dc=ldap,dc=example,dc=com`
|
|
26
|
+
- **Bind DN** — e.g. `uid=admin,cn=users,dc=ldap,dc=example,dc=com`
|
|
27
|
+
- **Bind password** — stored in OS keychain
|
|
28
|
+
|
|
29
|
+
Config saved to `~/.ldapc/config.yaml` (mode 0600).
|
|
30
|
+
|
|
31
|
+
### list
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
ldapc list users # list all users
|
|
35
|
+
ldapc list groups # list all groups
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### search
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ldapc search user <term> # case-insensitive substring match on cn/uid
|
|
42
|
+
ldapc search group <term> # case-insensitive substring match on cn
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### get
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ldapc get user <name> # exact match on uid or cn
|
|
49
|
+
ldapc get group <name> # exact match on cn
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Returns exit code 1 if not found.
|
|
53
|
+
|
|
54
|
+
### add
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ldapc add user <name> # creates inetOrgPerson under ou=users
|
|
58
|
+
ldapc add group <name> # creates posixGroup under ou=groups
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### delete
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ldapc delete user <name>
|
|
65
|
+
ldapc delete group <name>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### user (group membership)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ldapc user add group <user> <group> # add user to group
|
|
72
|
+
ldapc user remove group <user> <group> # remove user from group
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Output Formats
|
|
76
|
+
|
|
77
|
+
### Default (text)
|
|
78
|
+
|
|
79
|
+
Aligned key-value format:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
DN : uid=jdoe,ou=users,dc=example,dc=com
|
|
83
|
+
CN : John Doe
|
|
84
|
+
Email : john@example.com
|
|
85
|
+
Groups : cn=developers,ou=groups,dc=example,dc=com
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### JSON (`--json`)
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
[
|
|
92
|
+
{
|
|
93
|
+
"dn": "uid=jdoe,ou=users,dc=example,dc=com",
|
|
94
|
+
"attributes": {
|
|
95
|
+
"cn": ["John Doe"],
|
|
96
|
+
"mail": ["john@example.com"],
|
|
97
|
+
"memberOf": ["cn=developers,ou=groups,dc=example,dc=com"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### YAML (`--yaml`)
|
|
104
|
+
|
|
105
|
+
Simplified, human-friendly format. Group DNs are reduced to just the cn value:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
---
|
|
109
|
+
cn: John Doe
|
|
110
|
+
email: john@example.com
|
|
111
|
+
groups:
|
|
112
|
+
- developers
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Global Flags
|
|
116
|
+
|
|
117
|
+
These flags work with any subcommand and can appear anywhere after the subcommand name:
|
|
118
|
+
|
|
119
|
+
| Flag | Description |
|
|
120
|
+
|------|-------------|
|
|
121
|
+
| `--debug` | Enable debug logging (connection details, search filters, result counts) to stderr |
|
|
122
|
+
| `--skip-ssl-verify` | Skip TLS certificate verification |
|
|
123
|
+
| `--json` | Output results as JSON |
|
|
124
|
+
| `--yaml` | Output results as simplified YAML |
|
|
125
|
+
|
|
126
|
+
## Environment Variables
|
|
127
|
+
|
|
128
|
+
| Variable | Description |
|
|
129
|
+
|----------|-------------|
|
|
130
|
+
| `LDAPC_SKIP_SSL_VERIFY` | Set to `1`, `true`, or `yes` to skip TLS cert verification by default |
|
|
131
|
+
|
|
132
|
+
## Configuration
|
|
133
|
+
|
|
134
|
+
### File location
|
|
135
|
+
|
|
136
|
+
`~/.ldapc/config.yaml` (created by `ldapc configure`)
|
|
137
|
+
|
|
138
|
+
### Format
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
host: ldaps://ldap.example.com:636
|
|
142
|
+
username: uid=admin,cn=users,dc=ldap,dc=example,dc=com
|
|
143
|
+
base_dn: dc=ldap,dc=example,dc=com
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Credentials
|
|
147
|
+
|
|
148
|
+
Password is stored in the OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service) under service name `ldapc`.
|
|
149
|
+
|
|
150
|
+
## Installation
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# From PyPI
|
|
154
|
+
pip install ldap-cli
|
|
155
|
+
|
|
156
|
+
# From source (editable, with dev deps)
|
|
157
|
+
pip install -e ".[dev]"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Development
|
|
161
|
+
|
|
162
|
+
### Running tests
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pip install -e ".[dev]"
|
|
166
|
+
python3 -m pytest tests/ -v
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Coverage
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
python3 -m pytest tests/ --cov=ldapc --cov-report=term-missing
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Project structure
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
ldapc/
|
|
179
|
+
__init__.py # Package init, exports __version__
|
|
180
|
+
_version.py # Version extraction from CHANGELOG.md
|
|
181
|
+
cli.py # CLI entry point with subcommand parser
|
|
182
|
+
config.py # Config file and keyring management
|
|
183
|
+
ldap_client.py # LdapClient class (connect, search, CRUD)
|
|
184
|
+
formatter.py # Output formatting (text, JSON, YAML)
|
|
185
|
+
exceptions.py # Exception hierarchy
|
|
186
|
+
tests/ # Unit tests (pytest + hypothesis)
|
|
187
|
+
docs/ # This documentation
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Adding a new subcommand
|
|
191
|
+
|
|
192
|
+
1. Add the subparser in `build_parser()` in `cli.py`
|
|
193
|
+
2. Create a `_handle_<command>()` function
|
|
194
|
+
3. Add dispatch in `main()`
|
|
195
|
+
4. Add corresponding method(s) to `LdapClient` if needed
|
|
196
|
+
5. Add tests in `tests/test_cli.py`
|
|
197
|
+
6. Update this documentation
|
|
198
|
+
7. Add changelog entry under `[Unreleased]`
|