Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Refactor ldap-sync + implement new sync sources #49

Merged
merged 4 commits into from
Sep 16, 2024

Conversation

jannden
Copy link
Contributor

@jannden jannden commented Sep 2, 2024

Closes #2373

@CLAassistant
Copy link

CLAassistant commented Sep 2, 2024

CLA assistant check
All committers have signed the CLA.

@jannden
Copy link
Contributor Author

jannden commented Sep 2, 2024

This is the first proposition for how to approach extending the ldap-sync.

Here, using a partner's endpoint to get the emails of users that they want us to delete from Zitadel, we wouldn't need LDAP at all. The library zitadel_rust_client would need an additional method delete_user_by_email.

One thing I am pondering -> if taking this approach and sidestepping the LDAP altogether, this might eventually become a zitadel-sync instead of ldap-sync :)

src/config.rs Outdated Show resolved Hide resolved
src/main.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@tlater-famedly
Copy link
Contributor

tlater-famedly commented Sep 2, 2024

Here, using a partner's endpoint to get the emails of users that they want us to delete from Zitadel, we wouldn't need LDAP at all. The library zitadel_rust_client would need an additional method delete_user_by_email.

Yeah, that's the idea. For situations where the deactivate-only source is used, LDAP isn't relevant. I'm also fairly certain that method exists already, I think I used it in tests?

One thing I am pondering -> if taking this approach and sidestepping the LDAP altogether, this might eventually become a zitadel-sync instead of ldap-sync :)

Yep, we agreed to rename this to famedly-sync-client.


I think the primary difficulty is finding the correct level of abstraction for the "source" concept. Maybe it's enough to just implement lots of ldap.rs, deactivate-list.rs, etc., and then manually call a method for each, and then doing a complex union between sets. otoh, perhaps we should implement a trait to do that properly, so we can implement new source without touching lib.rs. Maybe the config parsing for each source should live in its specific module, rather than in a top-level config.rs. These are all things to explore.

Tests will also likely become more complex. For this particular source, wiremock is probably the way to go, but for others we'll probably need other docker test environments, which should be launched when appropriate with test groups so that the services don't interfere with each other.

@jannden
Copy link
Contributor Author

jannden commented Sep 2, 2024

Okay, makes perfect sense then :)

@sirewix
Copy link
Contributor

sirewix commented Sep 2, 2024

I think the better approach would be to not separate deactivate_only feature into "from ldap" and "from endpoint" (although according to the issue description this design would suffice), but rather abstract in a way that would allow to recieve changes from different source by using different adaptors.

trait DataSource {
  async fn get_data() -> Stream<DataEntry>; // or Vec
}

fn asdf() {
  let changes = match config.source {
    Ldap => LdapSource::new().get_data(),
    Endpoint => Endpoint::new().get_data(),
  };
  ...
}

However if we want to combine a few sources, that might be tricky:

struct DataSourcesConfig {
  get_new: DataSource,
  get_updated: DataSource,
  get_deleted: DataSource,
}

src/zitadel.rs Outdated Show resolved Hide resolved
src/sync_endpoint.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/sync_endpoint.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@jannden
Copy link
Contributor Author

jannden commented Sep 3, 2024

Major changes:

  1. I untangled the User from LDAP, so that there is a clear distinction between the Zitadel part and the LDAP part.
  2. Based on the comments, I evolved the trait to focus on the Source logic only, without touching anything Zitadel related. The Zitadel logic lives separately and is called directly from the lib.rs.

Copy link
Contributor

@tlater-famedly tlater-famedly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going in the right direction, sorry, I know you're not done but had a skim through since I saw the most recent comment

src/lib.rs Outdated Show resolved Hide resolved
src/source_ldap.rs Outdated Show resolved Hide resolved
src/source_list.rs Outdated Show resolved Hide resolved
tests/e2e.rs Outdated Show resolved Hide resolved
@emgrav emgrav mentioned this pull request Sep 4, 2024
@jannden jannden force-pushed the jannden/deactivate-feature-for-ukt branch from 6f3edb9 to 5081d0e Compare September 4, 2024 13:13
src/config.rs Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/source_list.rs Outdated Show resolved Hide resolved
src/source_list.rs Outdated Show resolved Hide resolved
src/source_list.rs Outdated Show resolved Hide resolved
@sirewix
Copy link
Contributor

sirewix commented Sep 4, 2024

The provided api spec for deactivation endpoint is extremely client specific, I think we should either name that data source using client name (like UKTCustomDataSource) or invent a generic configurable format to cover all use cases. The latter seems excessive, especially at this point, so I'd say we should implement simple ad-hoc data source and name it accordingly to client

@jannden
Copy link
Contributor Author

jannden commented Sep 4, 2024

Thanks for the comments. Valid points, good perspectives. I like how it's crystalizing. Will come up with next version soon-ish.

@jannden jannden changed the title Feat: Implement Deactivation Feature from an Endpoint Feat: Refactor ldap-sync + implement new sync sources Sep 4, 2024
@jannden
Copy link
Contributor Author

jannden commented Sep 4, 2024

I will nudge the next version in a slightly new direction. To give it a fresh perspective. Because when we step back for a moment, do we really think that having a trait for unifying the sources actually makes sense?

Consider the fact that the sources are too diverse. They can supply a completely different matrix of data combinations. At this moment, we have only two sources, and already now it's tangled with added, changed, removed on one side VS for each of those sometimes it’s user ID, sometimes LDAP Search Entry, sometimes email address. That results in a huge amount of combinations and unnecessary complexity trying to tie it up with a single get_all method.

Let me present a simplified solution tomorrow and you will let me know how you feel about it.

@jannden
Copy link
Contributor Author

jannden commented Sep 5, 2024

There is a question we need to address.

So, UKT provides us with a list of email we should delete from Zitadel. In zitadel-rust-client, I see we use email interchangeably with login_name (at least in the e2e tests written for ldap-sync. So I want to confirm this.

We have three methods to get users at the moment in zitadel-rust-client:

  • get_user_by_id
  • get_user_by_login_name
  • get_user_by_nick_name

I assume this might change quite soon, when the zitadel-rust-client gets updated to zitadel's V2.

But the question remains for this PR: is supplying an email to get_user_by_login_name find the users we want to delete as per UKT's request?

Is there a way to test this in a real environment to be sure?

@sirewix
Copy link
Contributor

sirewix commented Sep 5, 2024

Maybe abstracting over identifiers can be an option?

enum Id {
  Login(String),
  Nick(String),
  Id(String),
}

or

enum IdType { Login, Nick, Id }
type Id = (String, IdType)

But it has to be tested whether this possible to implement

@jannden
Copy link
Contributor Author

jannden commented Sep 5, 2024

@sirewix I don't want to discuss the implementation in zitadel_rust_client. My question simply is (if you are familiar with the matter), whether this will work:

let user = self.zitadel_client.get_user_by_login_name(email).await?;

It most probably will work, because this is a code that has been in the zitadel.rs from before, so I base my logic on that:

	async fn get_user_id(&self, user: &User) -> Result<Option<String>> {
		let status =
			self.zitadel_client.get_user_by_login_name(&user.email.clone().to_string()).await;

		if let Err(ZitadelError::TonicResponseError(ref error)) = status {
			if error.code() == TonicErrorCode::NotFound {
				return Ok(None);
			}
		}

		Ok(status.map(|user| user.map(|u| u.id))?)
	}

@jannden
Copy link
Contributor Author

jannden commented Sep 5, 2024

So I pushed a new version.

This is a solution that makes more sense to me. That is:

  • without using an unnecessary trait that tries to unify sources that are too diverse,
  • without trying to make the endpoint generic in case this is a small and custom made implementation for UKT.

Please have a look and hopefully this makes sense to you as well.

@jannden
Copy link
Contributor Author

jannden commented Sep 5, 2024

Another question - is it necessary to update .env or config.yaml in Github, so that the tests start passing? They do pass when I run them locally.

@jannden
Copy link
Contributor Author

jannden commented Sep 5, 2024

I added CSV Source support as well as per /issues/52

@jannden
Copy link
Contributor Author

jannden commented Sep 5, 2024

Pending: e2e tests for new sources.

@nikzen
Copy link

nikzen commented Sep 5, 2024

There is a question we need to address.

So, UKT provides us with a list of email we should delete from Zitadel. In zitadel-rust-client, I see we use email interchangeably with login_name (at least in the e2e tests written for ldap-sync. So I want to confirm this.

We have three methods to get users at the moment in zitadel-rust-client:

  • get_user_by_id
  • get_user_by_login_name
  • get_user_by_nick_name

I assume this might change quite soon, when the zitadel-rust-client gets updated to zitadel's V2.

But the question remains for this PR: is supplying an email to get_user_by_login_name find the users we want to delete as per UKT's request?

Is there a way to test this in a real environment to be sure?

I have setup the connection and the mail is used as loginname

@jannden
Copy link
Contributor Author

jannden commented Sep 6, 2024

Thanks for the confirmation, Niklas 👍

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/sources/csv.rs Outdated Show resolved Hide resolved
src/sources/csv.rs Outdated Show resolved Hide resolved
src/sources/ldap.rs Outdated Show resolved Hide resolved
src/sources/mod.rs Outdated Show resolved Hide resolved
src/sources/ukt.rs Outdated Show resolved Hide resolved
src/sources/ukt.rs Outdated Show resolved Hide resolved
src/sources/ukt.rs Outdated Show resolved Hide resolved
@jannden jannden force-pushed the jannden/deactivate-feature-for-ukt branch from f3b5c70 to 4e5d0c5 Compare September 12, 2024 11:41
@jannden jannden marked this pull request as ready for review September 12, 2024 11:46
@jannden jannden requested a review from a team as a code owner September 12, 2024 11:46
@jannden jannden force-pushed the jannden/deactivate-feature-for-ukt branch from 4e5d0c5 to ae94c48 Compare September 12, 2024 13:47
Copy link

github-actions bot commented Sep 12, 2024

LCOV of commit 62fe051 during Rust workflow #329

Summary coverage rate:
  lines......: 85.7% (956 of 1116 lines)
  functions..: 49.3% (230 of 467 functions)
  branches...: no data found

Files changed coverage rate:
                     |Lines       |Functions  |Branches    
  Filename           |Rate     Num|Rate    Num|Rate     Num
  =========================================================
  src/config.rs      |96.2%    209|54.1%    98|    -      0
  src/main.rs        | 0.0%     31| 0.0%    10|    -      0
  src/sources/ldap.rs|95.9%    319|59.3%   135|    -      0
  src/sources/ukt.rs |86.7%    211|59.5%    84|    -      0
  src/user.rs        |85.3%     68|45.5%    22|    -      0
  src/zitadel.rs     |74.8%    278|31.4%   118|    -      0

src/sources.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/sources/csv.rs Outdated Show resolved Hide resolved
@jannden jannden force-pushed the jannden/deactivate-feature-for-ukt branch 2 times, most recently from 74b7c35 to c60bd80 Compare September 13, 2024 09:17
src/sources/ukt.rs Outdated Show resolved Hide resolved
@sirewix
Copy link
Contributor

sirewix commented Sep 13, 2024

LGTM except few nits 👍

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
config.sample.yaml Show resolved Hide resolved
src/sources/ukt.rs Outdated Show resolved Hide resolved
tests/e2e.rs Show resolved Hide resolved
tests/e2e.rs Outdated Show resolved Hide resolved
tests/e2e.rs Show resolved Hide resolved
sirewix
sirewix previously approved these changes Sep 16, 2024
config.sample.yaml Outdated Show resolved Hide resolved
@jannden jannden force-pushed the jannden/deactivate-feature-for-ukt branch from 9b2bf19 to 62fe051 Compare September 16, 2024 09:28
@jannden jannden merged commit 62fe051 into main Sep 16, 2024
3 checks passed
@jannden jannden deleted the jannden/deactivate-feature-for-ukt branch September 16, 2024 11:32
@sirewix sirewix mentioned this pull request Sep 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants