<html>
  <head>
    <meta content="text/html; charset=windows-1252"
      http-equiv="Content-Type">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    <div class="moz-cite-prefix">On 16/06/16 16:38, Bill Burke wrote:<br>
    </div>
    <blockquote
      cite="mid:889fa832-c988-948c-2842-5348922f07dc@redhat.com"
      type="cite">
      <blockquote type="cite" style="color: #000000;">
        <blockquote type="cite" style="color: #000000;">
          <blockquote type="cite" style="color: #000000;">Transactions
            and 3rd party updates
            <br>
            -----------------------------------------------
            <br>
            <br>
            - Will be good to improve registration of user to LDAP.
            Ideally during
            <br>
            registration new user to LDAP, we should allow to send all
            data at once.
            <br>
            (currently UserFederationProvider.register supports sending
            just
            <br>
            username). Also we should allow to specify if register to
            3rd party
            <br>
            provider should be done <b class="moz-txt-star"><span
                class="moz-txt-tag">*</span>before<span
                class="moz-txt-tag">*</span></b> or <b
              class="moz-txt-star"><span class="moz-txt-tag">*</span>after<span
                class="moz-txt-tag">*</span></b> the registration to
            Keycloak
            <br>
            local DB. For details, see <a moz-do-not-send="true"
              class="moz-txt-link-freetext"
              href="https://issues.jboss.org/browse/KEYCLOAK-1075">https://issues.jboss.org/browse/KEYCLOAK-1075</a>
            <br>
            and all the comments from users...
            <br>
            <br>
            - Also updating user should be ideally at once. For example
            if you call:
            <br>
            user.setFirstName("john");
            <br>
            user.setLastName("Doe");
            <br>
            user.setEmail(<a moz-do-not-send="true"
              class="moz-txt-link-rfc2396E" href="mailto:john@email.cz">"john@email.cz"</a>);
            <br>
            <br>
            we shouldn't have 3 update calls to LDAP, but just one.
            Maybe we can
            <br>
            address this with transaction abstraction? I've already did
            something
            <br>
            for LDAP provider (see TxAwareLDAPUserModelDelegate ),
            however will be
            <br>
            good to provide something more generic for user storage SPI.
            Then when
            <br>
            KeycloakTransactionManager.commit is called, the data are
            send to
            <br>
            federation storage
            <br>
            <br>
          </blockquote>
          <br>
          This would be an implementation detail.  Similar to JPA Users,
          the
          <br>
          LDAP UserAdapter will be remembered per session.  The LDAP
          adapter
          <br>
          could queue up updates then at commit time flush them all.
          <br>
          <br>
          Maybe you should consider writing an LDAP version of JPA?  <span
            class="moz-smiley-s3" title=";-)"><span>;-)</span></span>
          <br>
          Probably a lot of code could be borrowed from PL IDM API.
          <br>
        </blockquote>
        I've wrote already something similar for Mongo <span
          class="moz-smiley-s3" title=";)"><span>;)</span></span> Also I
        already
        <br>
        addressed the multiple-updates issue for LDAP (see above).
        However this
        <br>
        one is mainly issue for 3rd party providers rather than for LDAP
        <br>
        provider. Especially since 3rd party provider may have some
        validations
        <br>
        for some attributes.
        <br>
        <br>
        <br>
        For example consider this scenario, which can happen in current
        <br>
        implementation:
        <br>
        <br>
        UserModel john = session.users().addUser(realm, "john");
        <br>
        john.setEmail(<a moz-do-not-send="true"
          class="moz-txt-link-rfc2396E" href="mailto:john@email.org">"john@email.org"</a>);
        <br>
        <br>
        All of this happens during "addUser" call:
        <br>
        <br>
        - User "john" is saved to local-db
        <br>
        - SomeUserFederationProvider.register calls the registration
        request to
        <br>
        3rd party federation storage. User "john" is saved in 3rd party
        storage
        <br>
        now.
        <br>
        <br>
        Then john.setEmail is invoked
        <br>
        - There is request to 3rd party federation storage to update
        email.
        <br>
        - 3rd party storage refuse to save the email because there is
        already an
        <br>
        existing user with email <a moz-do-not-send="true"
          class="moz-txt-link-rfc2396E" href="mailto:john@email.org">"john@email.org"</a>
        . So it sends back the
        <br>
        validation error. Now keycloak can rollback the transaction,
        however
        <br>
        user "john" was already registered in 3rd party federation
        storage from
        <br>
        previous "register" call -&gt; FAIL
        <br>
        <br>
        So now you need to manually remove the user from 3rd party
        storage,
        <br>
        which is dirty hack. There are other possible hacks to address
        this. For
        <br>
        example someone "postpone" the registration in "register" method
        by
        <br>
        adding some helper attributes and sends the registration request
        later
        <br>
        when all the attributes are set. I suggest to read the comments
        in
        <br>
        <a moz-do-not-send="true" class="moz-txt-link-freetext"
          href="https://issues.jboss.org/browse/KEYCLOAK-1075">https://issues.jboss.org/browse/KEYCLOAK-1075</a>
        for details.
        <br>
        <br>
        IMO the proper solution is to ensure that "register" request to
        3rd
        <br>
        party is postponed until the transaction.commit when user is
        filled with
        <br>
        all the attributes. Similarly update should be done just once.
        IMO we
        <br>
        should provide something at SPI level to simplify this scenario.
        <br>
        <br>
      </blockquote>
      <br>
      We already have a KeycloakTransaction you can register with.
      <br>
      <br>
      <blockquote type="cite" style="color: #000000;">I understand that
        main motivation for you is to avoid "imports", however
        <br>
        since we are refactoring anyway, we should try to address other
        SPI
        <br>
        limitations too. And IMO this one is pretty bad and worth
        improve <span class="moz-smiley-s3" title=";)"><span>;)</span></span>
        <br>
      </blockquote>
      <br>
      So, have a method addUser(RealmModel realm, UserEntity user)?
    </blockquote>
    I am thinking about adding some helper classes, so the people don't
    need to directly deal with our transaction API and manually enlist
    transactions in their code etc.<br>
    <br>
    Also it will be good to have some support for validations (aka.
    pseudo 2-phase commit), so that all "small" userStores have
    possibility to first validate if created/updated user is valid for
    them and then the real update is send. This will help to avoid
    situation like:<br>
    <meta http-equiv="content-type" content="text/html;
      charset=windows-1252">
    - transaction.commit is invoked<br>
    - store1 successfully register the user and commits<br>
    - store2 rejects to register user because of some validation error
    (ie. duplicated email)<br>
    - now some data of invalid user already saved in store1 -&gt; FAIL .
    Because at this point, whole transaction should be rollback and user
    shouldn't be saved anywhere.<br>
    <br>
    <br>
    I am thinking about pseudo-code similar to this:<br>
    <br>
    public abstract class FederationTransaction implements
    KeycloakTransaction {<br>
    <br>
        protected TransactionState state = TransactionState.NOT_STARTED;<br>
        protected final TxUserStore userStore;<br>
        protected final TxUserModel user;<br>
    <br>
        public FederationTransaction(TxUserStore userStore, TxUserModel
    user) {<br>
            this.userStore = userStore;<br>
            this.user = user;<br>
        }<br>
    <br>
        // Implement common stuff like begin, rollback,
    setRollbackOnly...<br>
    <br>
    }<br>
    <br>
    <br>
    // This transaction just validates in 3rd party store if user is ok<br>
    public class ValidationTransaction extends FederationTransaction {<br>
    <br>
        public ValidationTransaction(TxUserStore userStore, TxUserModel
    user) {<br>
            super(userStore, user);<br>
        }<br>
    <br>
        @Override<br>
        protected void commit() {<br>
            userStore.validateUser(user);<br>
        }<br>
    <br>
    }<br>
    <br>
    <br>
    // This transaction finally sends commit to 3rd party store<br>
    public class CommitTransaction extends FederationTransaction {<br>
    <br>
        public CommitTransaction(TxUserStore userStore, TxUserModel
    user) {<br>
            super(userStore, user);<br>
        }<br>
    <br>
        @Override<br>
        protected void commit() {<br>
            userStore.createOrUpdateUser(user);<br>
        }<br>
    <br>
    }<br>
    <br>
    <br>
    public abstract class TxUserStore implements UserStore {<br>
    <br>
        private final KeycloakSession session;<br>
    <br>
        public TxUserStore(KeycloakSession session) {<br>
            this.session = session;<br>
        }<br>
    <br>
        public KeycloakSession getSession() {<br>
            return session;<br>
        }<br>
    <br>
        // Send request to storage to validate user<br>
        protected abstract void validateUser(TxUserModel user) throws
    FederationValidationException;<br>
    <br>
        // Send request to create or update user during
    transaction.commit when all stores successfully validated user<br>
        protected abstract void createOrUpdateUser(TxUserModel user);<br>
    }<br>
    <br>
    <br>
    <br>
    public class TxUserModel extends UserModelDelegate {<br>
    <br>
        private ValidationTransaction validationTransaction;<br>
        private CommitTransaction commitTransaction;<br>
    <br>
        private final TxUserStore store;<br>
    <br>
        public TxUserModel(UserModel delegate, TxUserStore store) {<br>
            super(delegate);<br>
            this.store = store;<br>
        }<br>
        <br>
    <br>
        protected void ensureTransactionEnlisted() {<br>
            if (commitTransaction == null) {<br>
                validationTransaction = new ValidationTransaction(store,
    this);<br>
                commitTransaction = new CommitTransaction(store, this);<br>
    <br>
                // Ensure all validation transactions are called before
    sending real "commit" to any storage<br>
               
store.getSession().getTransaction().enlistPrepare(validationTransaction);<br>
    <br>
                // Enlisting real transaction here<br>
               
store.getSession().getTransaction().enlistAfterCompletion(commitTransaction);<br>
            }<br>
        }<br>
        <br>
    }<br>
    <br>
    <br>
    <br>
    Now in your real store implementation, you need to do just this to
    ensure that registration of user is postponed to transaction commit:<br>
    <br>
    public UserModel register(UserModel register(RealmModel realm,
    UserModel user) {<br>
      MyTxUserModel wrappedUser = new MyTxUserModel(user, this);<br>
      wrappedUser.ensureTransactionEnlisted();<br>
    }<br>
    <br>
    <br>
    And also ensure that transaction is enlisted during any update
    calls. So in MyTxUserModel override the needed setter methods like
    this:<br>
    <br>
       @Override<br>
       public void setFirstName(String firstName) {<br>
            ensureTransactionEnlisted();<br>
            super.setFirstName(firstName);<br>
        }<br>
    <br>
        @Override<br>
        public void setLastName(String lastName) {<br>
            ensureTransactionEnlisted();<br>
            super.setLastName(lastName);<br>
        }<br>
    <br>
    <br>
    With this approach, when you have:<br>
    UserModel john = session.users().addUser("john", realm);<br>
    john.setFirstName("John");<br>
    john.setLastName("Doe");<br>
    <br>
    you have the register call postponed until the transaction.commit .
    <br>
    <br>
    The thing is, that with postponed registration you won't know the ID
    of newly created user at the moment when "addUser" is called.
    However not sure if that's big issue... Another approach might be
    that UserStore will have possibility to specify if it supports
    transactions or not. Then we can have method "rollback" on
    UserStore, which will be called if keycloakTransaction.rollback is
    called.<br>
    <br>
    Marek<br>
    <meta http-equiv="content-type" content="text/html;
      charset=windows-1252">
  </body>
</html>