Skip to content

Commit

Permalink
✨ Add basic Apricot server
Browse files Browse the repository at this point in the history
  • Loading branch information
jemrobinson committed Sep 26, 2023
1 parent f511b5e commit 8975f44
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 0 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@

`Apricot` is a proxy for delegating LDAP requests to an OpenID Connect backend.
The name is a slightly tortured acronym for: LD**A**P **pr**oxy for Open**I**D **Co**nnec**t**.

## Usage

Start the `Apricot` server on port 8080 by running:

```bash
python run.py --port 8080
```
2 changes: 2 additions & 0 deletions apricot/__about__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__version__ = "0.0.1"
__version_info__ = tuple(__version__.split("."))
8 changes: 8 additions & 0 deletions apricot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .apricot_server import ApricotServer
from .__about__ import __version__, __version_info__

__all__ = [
"__version__",
"__version_info__",
"ApricotServer",
]
Binary file added apricot/__pycache__/__about__.cpython-310.pyc
Binary file not shown.
Binary file added apricot/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
32 changes: 32 additions & 0 deletions apricot/apricot_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sys
from typing import cast

from twisted.internet import reactor
from twisted.internet.endpoints import serverFromString
from twisted.internet.interfaces import IReactorCore, IStreamServerEndpoint
from twisted.python import log

from .ldap_lookup_tree import LDAPLookupTree
from .ldap_server_factory import LDAPServerFactory

class ApricotServer():
def __init__(self, port: int) -> None:
# Log to stdout
log.startLogging(sys.stdout)

# Initialize the LDAP lookup tree
tree = LDAPLookupTree()

# Create an LDAPServerFactory
factory = LDAPServerFactory(tree)

# Attach a listening endpoint
endpoint: IStreamServerEndpoint = serverFromString(reactor, f"tcp:{port}")
endpoint.listen(factory)

# Load the Twisted reactor
self.reactor = cast(IReactorCore, reactor)

def run(self) -> None:
"""Start the Twisted reactor"""
self.reactor.run()
25 changes: 25 additions & 0 deletions apricot/ldap_lookup_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from ldaptor.interfaces import IConnectedLDAPEntry, ILDAPEntry
from ldaptor.protocols.ldap.distinguishedname import DistinguishedName
from twisted.internet import defer
from twisted.python import log
from zope.interface import implementer

from apricot.proxied_ldap_entry import ProxiedLDAPEntry


@implementer(IConnectedLDAPEntry)
class LDAPLookupTree:
def lookup(self, dn: DistinguishedName | str) -> defer.Deferred[ILDAPEntry]:
"""
Lookup the referred to by dn.
@return: A Deferred returning an ILDAPEntry, or failing with e.g.
LDAPNoSuchObject.
"""

def _lookup(dn: DistinguishedName) -> ProxiedLDAPEntry:
return ProxiedLDAPEntry(dn, {})

if not isinstance(dn, DistinguishedName):
dn = DistinguishedName(stringValue=dn)
return defer.maybeDeferred(_lookup, dn)
17 changes: 17 additions & 0 deletions apricot/ldap_server_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from ldaptor.interfaces import IConnectedLDAPEntry
from ldaptor.protocols.ldap.ldapserver import LDAPServer
from twisted.internet.interfaces import IAddress
from twisted.internet.protocol import Protocol, ServerFactory


class LDAPServerFactory(ServerFactory):
protocol = LDAPServer

def __init__(self, adaptor: IConnectedLDAPEntry):
self.adaptor = adaptor

def buildProtocol(self, addr: IAddress) -> Protocol: # noqa: N802
id(addr) # ignore unused arguments
proto = self.protocol()
proto.factory = self.adaptor
return proto
19 changes: 19 additions & 0 deletions apricot/proxied_ldap_entry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Any

from ldaptor.inmemory import ReadOnlyInMemoryLDAPEntry
from ldaptor.protocols.ldap.ldaperrors import LDAPInvalidCredentials
from twisted.internet import defer


class ProxiedLDAPEntry(ReadOnlyInMemoryLDAPEntry):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)

def bind(self, password: bytes) -> defer.Deferred["ProxiedLDAPEntry"]:
def _bind(password: bytes) -> "ProxiedLDAPEntry":
if True:
print(f"Accepting password {password.decode('utf-8')} for {self.dn.getText()}") # noqa: T201
return self
raise LDAPInvalidCredentials()

return defer.maybeDeferred(_bind, password)
15 changes: 15 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import argparse

from apricot import ApricotServer

if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="Apricot",
description="Apricot is a proxy for delegating LDAP requests to an OpenID Connect backend.",
)
parser.add_argument("-p", "--port", type=int, default=8080)
args = parser.parse_args()

# Create the Apricot server
reactor = ApricotServer(port=args.port)
reactor.run()

0 comments on commit 8975f44

Please sign in to comment.