Skip to main content

kanidmd_lib/server/
migrations.rs

1use crate::prelude::*;
2
3use crate::migration_data;
4use kanidm_proto::internal::{
5    DomainUpgradeCheckItem as ProtoDomainUpgradeCheckItem,
6    DomainUpgradeCheckReport as ProtoDomainUpgradeCheckReport,
7    DomainUpgradeCheckStatus as ProtoDomainUpgradeCheckStatus,
8};
9
10use super::ServerPhase;
11
12impl QueryServer {
13    #[instrument(level = "info", name = "system_initialisation", skip_all)]
14    pub async fn initialise_helper(
15        &self,
16        ts: Duration,
17        domain_target_level: DomainVersion,
18    ) -> Result<(), OperationError> {
19        // We need to perform this in a single transaction pass to prevent tainting
20        // databases during upgrades.
21        let mut write_txn = self.write(ts).await?;
22
23        // Check our database version - attempt to do an initial indexing
24        // based on the in memory configuration. This ONLY triggers ONCE on
25        // the very first run of the instance when the DB in newely created.
26        write_txn.upgrade_reindex(SYSTEM_INDEX_VERSION)?;
27
28        // Because we init the schema here, and commit, this reloads meaning
29        // that the on-disk index meta has been loaded, so our subsequent
30        // migrations will be correctly indexed.
31        //
32        // Remember, that this would normally mean that it's possible for schema
33        // to be mis-indexed (IE we index the new schemas here before we read
34        // the schema to tell us what's indexed), but because we have the in
35        // mem schema that defines how schema is structured, and this is all
36        // marked "system", then we won't have an issue here.
37        if domain_target_level < DOMAIN_LEVEL_1_11 {
38            // We don't create these in the DB after 1_11
39            write_txn
40                .initialise_schema_core()
41                .and_then(|_| write_txn.reload())?;
42        }
43
44        // This is what tells us if the domain entry existed before or not. This
45        // is now the primary method of migrations and version detection.
46        let db_domain_version = match write_txn.internal_search_uuid(UUID_DOMAIN_INFO) {
47            Ok(e) => Ok(e.get_ava_single_uint32(Attribute::Version).unwrap_or(0)),
48            Err(OperationError::NoMatchingEntries) => Ok(0),
49            Err(r) => Err(r),
50        }?;
51
52        debug!(?db_domain_version, "Before setting internal domain info");
53
54        if db_domain_version == DOMAIN_LEVEL_0 {
55            // This is here to catch when we increase domain levels but didn't create the migration
56            // hooks. If this fails it probably means you need to add another migration hook
57            // in the above.
58            debug_assert!(domain_target_level <= DOMAIN_MAX_LEVEL);
59
60            // Assert that we have a minimum creation level that is valid.
61            const { assert!(DOMAIN_MIN_CREATION_LEVEL == DOMAIN_LEVEL_10) };
62
63            // No domain info was present, so neither was the rest of the IDM. Bring up the
64            // full IDM here.
65
66            match domain_target_level {
67                DOMAIN_LEVEL_10 => write_txn.migrate_domain_9_to_10()?,
68                DOMAIN_LEVEL_11 => write_txn.migrate_domain_10_to_11()?,
69                DOMAIN_LEVEL_12 => write_txn.migrate_domain_11_to_12()?,
70                DOMAIN_LEVEL_13 => write_txn.migrate_domain_12_to_13()?,
71                DOMAIN_LEVEL_14 => write_txn.migrate_domain_13_to_14()?,
72                DOMAIN_LEVEL_1_11 => write_txn.migrate_domain_1_10_to_1_11()?,
73                DOMAIN_LEVEL_1_12 => write_txn.migrate_domain_1_11_to_1_12()?,
74                _ => {
75                    error!("Invalid requested domain target level for server bootstrap");
76                    debug_assert!(false);
77                    return Err(OperationError::MG0009InvalidTargetLevelForBootstrap);
78                }
79            }
80
81            write_txn
82                .internal_apply_domain_migration(domain_target_level)
83                .map(|()| {
84                    warn!(
85                        "Domain level has been bootstrapped to {}",
86                        domain_target_level
87                    );
88                })?;
89        }
90
91        // These steps apply both to bootstrapping and normal startup, since we now have
92        // a DB with data populated in either path.
93
94        // Domain info is now present, so we need to reflect that in our server
95        // domain structures. If we don't do this, the in memory domain level
96        // is stuck at 0 which can confuse init domain info below.
97        //
98        // This also is where the former domain taint flag will be loaded to
99        // d_info so that if the *previous* execution of the database was
100        // a devel version, we'll still trigger the forced remigration in
101        // in the case that we are moving from dev -> stable.
102        write_txn.force_domain_reload();
103
104        write_txn.reload()?;
105
106        assert!(write_txn.get_domain_version() > DOMAIN_LEVEL_0);
107
108        // Indicate the schema is now ready, which allows dyngroups to work when they
109        // are created in the next phase of migrations.
110        write_txn.set_phase(ServerPhase::SchemaReady);
111
112        // #2756 - if we *aren't* creating the base IDM entries, then we
113        // need to force dyn groups to reload since we're now at schema
114        // ready. This is done indirectly by ... reloading the schema again.
115        //
116        // This is because dyngroups don't load until server phase >= schemaready
117        // and the reload path for these is either a change in the dyngroup entry
118        // itself or a change to schema reloading. Since we aren't changing the
119        // dyngroup here, we have to go via the schema reload path.
120        write_txn.force_schema_reload();
121
122        // Reload as init idm affects access controls.
123        write_txn.reload()?;
124
125        // Domain info is now ready and reloaded, we can proceed.
126        write_txn.set_phase(ServerPhase::DomainInfoReady);
127
128        // This is the start of domain info related migrations which we will need in future
129        // to handle replication. Due to the access control rework, and the addition of "managed by"
130        // syntax, we need to ensure both nodes "fence" replication from each other. We do this
131        // by changing domain infos to be incompatible during this phase.
132
133        // The reloads will have populated this structure now.
134        let domain_info_version = write_txn.get_domain_version();
135        let domain_patch_level = write_txn.get_domain_patch_level();
136        let domain_development_taint = write_txn.get_domain_development_taint();
137        debug!(
138            ?db_domain_version,
139            ?domain_patch_level,
140            ?domain_development_taint,
141            "After setting internal domain info"
142        );
143
144        let mut reload_required = false;
145
146        // If the database domain info is a lower version than our target level, we reload.
147        if domain_info_version < domain_target_level {
148            // if (domain_target_level - domain_info_version) > DOMAIN_MIGRATION_SKIPS {
149            if domain_info_version < DOMAIN_MIGRATION_FROM_MIN {
150                error!(
151                    "UNABLE TO PROCEED. You are attempting a skip update which is NOT SUPPORTED."
152                );
153                error!(
154                    "For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html"
155                );
156                error!(domain_previous_version = ?domain_info_version, domain_target_version = ?domain_target_level, domain_migration_minimum_limit = ?DOMAIN_MIGRATION_FROM_MIN);
157                return Err(OperationError::MG0008SkipUpgradeAttempted);
158            }
159
160            // Apply each step in order.
161            for domain_target_level_step in domain_info_version..domain_target_level {
162                // Rust has no way to do a range with the minimum excluded and the maximum
163                // included, so we have to do min -> max which includes min and excludes max,
164                // and by adding 1 we gett the same result.
165                let domain_target_level_step = domain_target_level_step + 1;
166                write_txn
167                    .internal_apply_domain_migration(domain_target_level_step)
168                    .map(|()| {
169                        warn!(
170                            "Domain level has been raised to {}",
171                            domain_target_level_step
172                        );
173                    })?;
174            }
175
176            // Reload if anything in migrations requires it - this triggers the domain migrations
177            // which in turn can trigger schema reloads etc. If the server was just brought up
178            // then we don't need the extra reload since we are already at the correct
179            // version of the server, and this call to set the target level is just for persistence
180            // of the value.
181            if domain_info_version != DOMAIN_LEVEL_0 {
182                reload_required = true;
183            }
184        } else if domain_info_version > domain_target_level {
185            // This is a DOWNGRADE which may not proceed.
186            error!("UNABLE TO PROCEED. You are attempting a downgrade which is NOT SUPPORTED.");
187            error!(
188                "For more see: https://kanidm.github.io/kanidm/stable/support.html#upgrade-policy and https://kanidm.github.io/kanidm/stable/server_updates.html"
189            );
190            error!(domain_previous_version = ?domain_info_version, domain_target_version = ?domain_target_level);
191            return Err(OperationError::MG0010DowngradeNotAllowed);
192        } else if domain_development_taint {
193            // This forces pre-release versions to re-migrate each start up. This solves
194            // the domain-version-sprawl issue so that during a development cycle we can
195            // do a single domain version bump, and continue to extend the migrations
196            // within that release cycle to contain what we require.
197            //
198            // If this is a pre-release build
199            // AND
200            // we are NOT in a test environment
201            // AND
202            // We did not already need a version migration as above
203            write_txn.domain_remigrate(DOMAIN_PREVIOUS_TGT_LEVEL)?;
204
205            reload_required = true;
206        }
207
208        // If we are new enough to support patches, and we are lower than the target patch level
209        // then a reload will be applied after we raise the patch level.
210        if domain_patch_level < DOMAIN_TGT_PATCH_LEVEL {
211            write_txn
212                .internal_modify_uuid(
213                    UUID_DOMAIN_INFO,
214                    &ModifyList::new_purge_and_set(
215                        Attribute::PatchLevel,
216                        Value::new_uint32(DOMAIN_TGT_PATCH_LEVEL),
217                    ),
218                )
219                .map(|()| {
220                    warn!(
221                        "Domain patch level has been raised to {}",
222                        domain_patch_level
223                    );
224                })?;
225
226            reload_required = true;
227        };
228
229        // Execute whatever operations we have batched up and ready to go. This is needed
230        // to preserve ordering of the operations - if we reloaded after a remigrate then
231        // we would have skipped the patch level fix which needs to have occurred *first*.
232        if reload_required {
233            write_txn.reload()?;
234        }
235
236        // Now set the db/domain devel taint flag to match our current release status
237        // if it changes. This is what breaks the cycle of db taint from dev -> stable
238        let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
239        if current_devel_flag {
240            warn!("Domain Development Taint mode is enabled");
241        }
242        if domain_development_taint != current_devel_flag {
243            write_txn.internal_modify_uuid(
244                UUID_DOMAIN_INFO,
245                &ModifyList::new_purge_and_set(
246                    Attribute::DomainDevelopmentTaint,
247                    Value::Bool(current_devel_flag),
248                ),
249            )?;
250        }
251
252        // We are ready to run
253        write_txn.set_phase(ServerPhase::Running);
254
255        // Commit all changes, this also triggers the final reload, this should be a no-op
256        // since we already did all the needed loads above.
257        write_txn.commit()?;
258
259        debug!("Database version check and migrations success! ☀️  ");
260        Ok(())
261    }
262}
263
264impl QueryServerWriteTransaction<'_> {
265    /// Apply a domain migration `to_level`. Errors if `to_level` is not greater than or equal to
266    /// the active level.
267    #[instrument(level = "debug", skip(self))]
268    pub(crate) fn internal_apply_domain_migration(
269        &mut self,
270        to_level: u32,
271    ) -> Result<(), OperationError> {
272        self.internal_modify_uuid(
273            UUID_DOMAIN_INFO,
274            &ModifyList::new_purge_and_set(Attribute::Version, Value::new_uint32(to_level)),
275        )
276        .and_then(|()| self.reload())
277    }
278
279    fn internal_migrate_or_create_batch(
280        &mut self,
281        msg: &str,
282        entries: Vec<EntryInitNew>,
283    ) -> Result<(), OperationError> {
284        #[cfg(test)]
285        eprintln!("MIGRATION BATCH: {}", msg);
286        let r: Result<(), _> = entries
287            .into_iter()
288            .try_for_each(|entry| self.internal_migrate_or_create(entry));
289
290        if let Err(err) = r {
291            error!(?err, msg);
292            debug_assert!(false);
293        }
294
295        Ok(())
296    }
297
298    #[instrument(level = "debug", skip_all)]
299    /// - If the thing exists:
300    ///   - Ensure the set of attributes match and are present
301    ///     (but don't delete multivalue, or extended attributes in the situation.
302    /// - If not:
303    ///   - Create the entry
304    ///
305    /// This will extra classes an attributes alone!
306    ///
307    /// NOTE: `gen_modlist*` IS schema aware and will handle multivalue correctly!
308    fn internal_migrate_or_create(
309        &mut self,
310        e: Entry<EntryInit, EntryNew>,
311    ) -> Result<(), OperationError> {
312        // NOTE: Ignoring an attribute only affects the migration phase, not create.
313        self.internal_migrate_or_create_ignore_attrs(
314            e,
315            &[
316                // If the credential type is present, we don't want to touch it.
317                Attribute::CredentialTypeMinimum,
318            ],
319        )
320    }
321
322    #[instrument(level = "debug", skip_all)]
323    fn internal_delete_batch(
324        &mut self,
325        msg: &str,
326        entries: Vec<Uuid>,
327    ) -> Result<(), OperationError> {
328        let filter = entries
329            .into_iter()
330            .map(|uuid| f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)))
331            .collect();
332
333        // Don't attempt to delete already removed entries that are in the recycle bin.
334        let filter = filter!(f_or(filter));
335
336        let result = self.internal_delete(&filter);
337
338        match result {
339            Ok(_) | Err(OperationError::NoMatchingEntries) => Ok(()),
340            Err(err) => {
341                error!(?err, msg);
342                Err(err)
343            }
344        }
345    }
346
347    /// This is the same as [QueryServerWriteTransaction::internal_migrate_or_create]
348    /// but it will ignore the specified list of attributes, so that if an admin has
349    /// modified those values then we don't stomp them.
350    #[instrument(level = "trace", skip_all)]
351    fn internal_migrate_or_create_ignore_attrs(
352        &mut self,
353        mut e: Entry<EntryInit, EntryNew>,
354        attrs: &[Attribute],
355    ) -> Result<(), OperationError> {
356        trace!("operating on {:?}", e.get_uuid());
357
358        let Some(filt) = e.filter_from_attrs(&[Attribute::Uuid]) else {
359            return Err(OperationError::FilterGeneration);
360        };
361
362        trace!("search {:?}", filt);
363
364        let results = self.internal_search(filt.clone())?;
365
366        if results.is_empty() {
367            // The entry does not exist. Create it.
368
369            // If there are create-once members, set them up now.
370            if let Some(members_create_once) = e.pop_ava(Attribute::MemberCreateOnce) {
371                if let Some(members) = e.get_ava_mut(Attribute::Member) {
372                    // Merge
373                    members.merge(&members_create_once).inspect_err(|err| {
374                        error!(?err, "Unable to merge member sets, mismatched types?");
375                    })?;
376                } else {
377                    // Just push
378                    e.set_ava_set(&Attribute::Member, members_create_once);
379                }
380            };
381
382            self.internal_create(vec![e])
383        } else if results.len() == 1 {
384            // This is always ignored during migration.
385            e.remove_ava(&Attribute::MemberCreateOnce);
386
387            // For each ignored attr, we remove it from entry.
388            for attr in attrs.iter() {
389                e.remove_ava(attr);
390            }
391
392            // If the thing is subset, pass
393            match e.gen_modlist_assert(&self.schema) {
394                Ok(modlist) => {
395                    // Apply to &results[0]
396                    trace!(?modlist);
397                    self.internal_modify(&filt, &modlist)
398                }
399                Err(e) => Err(OperationError::SchemaViolation(e)),
400            }
401        } else {
402            admin_error!(
403                "Invalid Result Set - Expected One Entry for {:?} - {:?}",
404                filt,
405                results
406            );
407            Err(OperationError::InvalidDbState)
408        }
409    }
410
411    // Commented as an example of patch application
412    /*
413    /// Patch Application - This triggers a one-shot fixup task for issue #3178
414    /// to force access controls to re-migrate in existing databases so that they're
415    /// content matches expected values.
416    #[instrument(level = "info", skip_all)]
417    pub(crate) fn migrate_domain_patch_level_2(&mut self) -> Result<(), OperationError> {
418        admin_warn!("applying domain patch 2.");
419
420        debug_assert!(*self.phase >= ServerPhase::SchemaReady);
421
422        let idm_data = migration_data::dl9::phase_7_builtin_access_control_profiles();
423
424        idm_data
425            .into_iter()
426            .try_for_each(|entry| self.internal_migrate_or_create(entry))
427            .map_err(|err| {
428                error!(?err, "migrate_domain_patch_level_2 -> Error");
429                err
430            })?;
431
432        self.reload()?;
433
434        Ok(())
435    }
436    */
437
438    /// Migration domain level 9 to 10 (1.6.0)
439    #[instrument(level = "info", skip_all)]
440    pub(crate) fn migrate_domain_9_to_10(&mut self) -> Result<(), OperationError> {
441        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
442            error!("Unable to raise domain level from 9 to 10.");
443            return Err(OperationError::MG0004DomainLevelInDevelopment);
444        }
445
446        // =========== Apply changes ==============
447        self.internal_migrate_or_create_batch(
448            "phase 1 - schema attrs",
449            migration_data::dl10::phase_1_schema_attrs(),
450        )?;
451
452        self.internal_migrate_or_create_batch(
453            "phase 2 - schema classes",
454            migration_data::dl10::phase_2_schema_classes(),
455        )?;
456
457        // Reload for the new schema.
458        self.reload()?;
459
460        // Since we just loaded in a ton of schema, lets reindex it in case we added
461        // new indexes, or this is a bootstrap and we have no indexes yet.
462        self.reindex(false)?;
463
464        // Set Phase
465        // Indicate the schema is now ready, which allows dyngroups to work when they
466        // are created in the next phase of migrations.
467        self.set_phase(ServerPhase::SchemaReady);
468
469        self.internal_migrate_or_create_batch(
470            "phase 3 - key provider",
471            migration_data::dl10::phase_3_key_provider(),
472        )?;
473
474        // Reload for the new key providers
475        self.reload()?;
476
477        self.internal_migrate_or_create_batch(
478            "phase 4 - system entries",
479            migration_data::dl10::phase_4_system_entries(),
480        )?;
481
482        // Reload for the new system entries
483        self.reload()?;
484
485        // Domain info is now ready and reloaded, we can proceed.
486        self.set_phase(ServerPhase::DomainInfoReady);
487
488        // Bring up the IDM entries.
489        self.internal_migrate_or_create_batch(
490            "phase 5 - builtin admin entries",
491            migration_data::dl10::phase_5_builtin_admin_entries()?,
492        )?;
493
494        self.internal_migrate_or_create_batch(
495            "phase 6 - builtin not admin entries",
496            migration_data::dl10::phase_6_builtin_non_admin_entries()?,
497        )?;
498
499        self.internal_migrate_or_create_batch(
500            "phase 7 - builtin access control profiles",
501            migration_data::dl10::phase_7_builtin_access_control_profiles(),
502        )?;
503
504        self.reload()?;
505
506        // =========== OAuth2 Cryptography Migration ==============
507
508        debug!("START OAUTH2 MIGRATION");
509
510        // Load all the OAuth2 providers.
511        let all_oauth2_rs_entries = self.internal_search(filter!(f_eq(
512            Attribute::Class,
513            EntryClass::OAuth2ResourceServer.into()
514        )))?;
515
516        if !all_oauth2_rs_entries.is_empty() {
517            let entry_iter = all_oauth2_rs_entries.iter().map(|tgt_entry| {
518                let entry_uuid = tgt_entry.get_uuid();
519                let mut modlist = ModifyList::new_list(vec![
520                    Modify::Present(Attribute::Class, EntryClass::KeyObject.to_value()),
521                    Modify::Present(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value()),
522                    Modify::Present(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value()),
523                    // Delete the fernet key, rs256 if any, and the es256 key
524                    Modify::Purged(Attribute::OAuth2RsTokenKey),
525                    Modify::Purged(Attribute::Es256PrivateKeyDer),
526                    Modify::Purged(Attribute::Rs256PrivateKeyDer),
527                ]);
528
529                trace!(?tgt_entry);
530
531                // Import the ES256 Key
532                if let Some(es256_private_der) =
533                    tgt_entry.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
534                {
535                    modlist.push_mod(Modify::Present(
536                        Attribute::KeyActionImportJwsEs256,
537                        Value::PrivateBinary(es256_private_der.to_vec()),
538                    ))
539                } else {
540                    warn!("Unable to migrate es256 key");
541                }
542
543                let has_rs256 = tgt_entry
544                    .get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable)
545                    .unwrap_or(false);
546
547                // If there is an rs256 key, import it.
548                // Import the RS256 Key
549                if has_rs256 {
550                    modlist.push_mod(Modify::Present(
551                        Attribute::Class,
552                        EntryClass::KeyObjectJwtEs256.to_value(),
553                    ));
554
555                    if let Some(rs256_private_der) =
556                        tgt_entry.get_ava_single_private_binary(Attribute::Rs256PrivateKeyDer)
557                    {
558                        modlist.push_mod(Modify::Present(
559                            Attribute::KeyActionImportJwsRs256,
560                            Value::PrivateBinary(rs256_private_der.to_vec()),
561                        ))
562                    } else {
563                        warn!("Unable to migrate rs256 key");
564                    }
565                }
566
567                (entry_uuid, modlist)
568            });
569
570            self.internal_batch_modify(entry_iter)?;
571        }
572
573        // Reload for new keys, and updated oauth2
574        self.reload()?;
575
576        // Done!
577
578        Ok(())
579    }
580
581    /// Migration domain level 10 to 11 (1.7.0)
582    #[instrument(level = "info", skip_all)]
583    pub(crate) fn migrate_domain_10_to_11(&mut self) -> Result<(), OperationError> {
584        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_10 {
585            error!("Unable to raise domain level from 10 to 11.");
586            return Err(OperationError::MG0004DomainLevelInDevelopment);
587        }
588
589        // =========== Apply changes ==============
590        self.internal_migrate_or_create_batch(
591            "phase 1 - schema attrs",
592            migration_data::dl11::phase_1_schema_attrs(),
593        )?;
594
595        self.internal_migrate_or_create_batch(
596            "phase 2 - schema classes",
597            migration_data::dl11::phase_2_schema_classes(),
598        )?;
599
600        // Reload for the new schema.
601        self.reload()?;
602
603        // Since we just loaded in a ton of schema, lets reindex it in case we added
604        // new indexes, or this is a bootstrap and we have no indexes yet.
605        self.reindex(false)?;
606
607        // Set Phase
608        // Indicate the schema is now ready, which allows dyngroups to work when they
609        // are created in the next phase of migrations.
610        self.set_phase(ServerPhase::SchemaReady);
611
612        self.internal_migrate_or_create_batch(
613            "phase 3 - key provider",
614            migration_data::dl11::phase_3_key_provider(),
615        )?;
616
617        // Reload for the new key providers
618        self.reload()?;
619
620        self.internal_migrate_or_create_batch(
621            "phase 4 - system entries",
622            migration_data::dl11::phase_4_system_entries(),
623        )?;
624
625        // Reload for the new system entries
626        self.reload()?;
627
628        // Domain info is now ready and reloaded, we can proceed.
629        self.set_phase(ServerPhase::DomainInfoReady);
630
631        // Bring up the IDM entries.
632        self.internal_migrate_or_create_batch(
633            "phase 5 - builtin admin entries",
634            migration_data::dl11::phase_5_builtin_admin_entries()?,
635        )?;
636
637        self.internal_migrate_or_create_batch(
638            "phase 6 - builtin not admin entries",
639            migration_data::dl11::phase_6_builtin_non_admin_entries()?,
640        )?;
641
642        self.internal_migrate_or_create_batch(
643            "phase 7 - builtin access control profiles",
644            migration_data::dl11::phase_7_builtin_access_control_profiles(),
645        )?;
646
647        self.reload()?;
648
649        Ok(())
650    }
651
652    /// Migration domain level 11 to 12 (1.8.0)
653    #[instrument(level = "info", skip_all)]
654    pub(crate) fn migrate_domain_11_to_12(&mut self) -> Result<(), OperationError> {
655        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_11 {
656            error!("Unable to raise domain level from 11 to 12.");
657            return Err(OperationError::MG0004DomainLevelInDevelopment);
658        }
659
660        // =========== Apply changes ==============
661        self.internal_migrate_or_create_batch(
662            "phase 1 - schema attrs",
663            migration_data::dl12::phase_1_schema_attrs(),
664        )?;
665
666        self.internal_migrate_or_create_batch(
667            "phase 2 - schema classes",
668            migration_data::dl12::phase_2_schema_classes(),
669        )?;
670
671        // Reload for the new schema.
672        self.reload()?;
673
674        // Since we just loaded in a ton of schema, lets reindex it in case we added
675        // new indexes, or this is a bootstrap and we have no indexes yet.
676        self.reindex(false)?;
677
678        // Set Phase
679        // Indicate the schema is now ready, which allows dyngroups to work when they
680        // are created in the next phase of migrations.
681        self.set_phase(ServerPhase::SchemaReady);
682
683        self.internal_migrate_or_create_batch(
684            "phase 3 - key provider",
685            migration_data::dl12::phase_3_key_provider(),
686        )?;
687
688        // Reload for the new key providers
689        self.reload()?;
690
691        self.internal_migrate_or_create_batch(
692            "phase 4 - system entries",
693            migration_data::dl12::phase_4_system_entries(),
694        )?;
695
696        // Reload for the new system entries
697        self.reload()?;
698
699        // Domain info is now ready and reloaded, we can proceed.
700        self.set_phase(ServerPhase::DomainInfoReady);
701
702        // Bring up the IDM entries.
703        self.internal_migrate_or_create_batch(
704            "phase 5 - builtin admin entries",
705            migration_data::dl12::phase_5_builtin_admin_entries()?,
706        )?;
707
708        self.internal_migrate_or_create_batch(
709            "phase 6 - builtin not admin entries",
710            migration_data::dl12::phase_6_builtin_non_admin_entries()?,
711        )?;
712
713        self.internal_migrate_or_create_batch(
714            "phase 7 - builtin access control profiles",
715            migration_data::dl12::phase_7_builtin_access_control_profiles(),
716        )?;
717
718        self.reload()?;
719
720        // Cleanup any leftover id keys
721        let modlist = ModifyList::new_purge(Attribute::IdVerificationEcKey);
722        let filter = filter_all!(f_pres(Attribute::IdVerificationEcKey));
723
724        self.internal_modify(&filter, &modlist)?;
725
726        Ok(())
727    }
728
729    /// Migration domain level 12 to 13 (1.9.0)
730    #[instrument(level = "info", skip_all)]
731    pub(crate) fn migrate_domain_12_to_13(&mut self) -> Result<(), OperationError> {
732        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_12 {
733            error!("Unable to raise domain level from 12 to 13.");
734            return Err(OperationError::MG0004DomainLevelInDevelopment);
735        }
736
737        // =========== Apply changes ==============
738        self.internal_migrate_or_create_batch(
739            "phase 1 - schema attrs",
740            migration_data::dl13::phase_1_schema_attrs(),
741        )?;
742
743        self.internal_migrate_or_create_batch(
744            "phase 2 - schema classes",
745            migration_data::dl13::phase_2_schema_classes(),
746        )?;
747
748        // Reload for the new schema.
749        self.reload()?;
750
751        // Since we just loaded in a ton of schema, lets reindex it in case we added
752        // new indexes, or this is a bootstrap and we have no indexes yet.
753        self.reindex(false)?;
754
755        // Set Phase
756        // Indicate the schema is now ready, which allows dyngroups to work when they
757        // are created in the next phase of migrations.
758        self.set_phase(ServerPhase::SchemaReady);
759
760        self.internal_migrate_or_create_batch(
761            "phase 3 - key provider",
762            migration_data::dl13::phase_3_key_provider(),
763        )?;
764
765        // Reload for the new key providers
766        self.reload()?;
767
768        self.internal_migrate_or_create_batch(
769            "phase 4 - system entries",
770            migration_data::dl13::phase_4_system_entries(),
771        )?;
772
773        // Reload for the new system entries
774        self.reload()?;
775
776        // Domain info is now ready and reloaded, we can proceed.
777        self.set_phase(ServerPhase::DomainInfoReady);
778
779        // Bring up the IDM entries.
780        self.internal_migrate_or_create_batch(
781            "phase 5 - builtin admin entries",
782            migration_data::dl13::phase_5_builtin_admin_entries()?,
783        )?;
784
785        self.internal_migrate_or_create_batch(
786            "phase 6 - builtin not admin entries",
787            migration_data::dl13::phase_6_builtin_non_admin_entries()?,
788        )?;
789
790        self.internal_migrate_or_create_batch(
791            "phase 7 - builtin access control profiles",
792            migration_data::dl13::phase_7_builtin_access_control_profiles(),
793        )?;
794
795        self.internal_delete_batch(
796            "phase 8 - delete UUIDS",
797            migration_data::dl13::phase_8_delete_uuids(),
798        )?;
799
800        self.reload()?;
801
802        Ok(())
803    }
804
805    /// Migration domain level 13 to 14 (1.10.0)
806    #[instrument(level = "info", skip_all)]
807    pub(crate) fn migrate_domain_13_to_14(&mut self) -> Result<(), OperationError> {
808        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_13 {
809            error!("Unable to raise domain level from 13 to 14.");
810            return Err(OperationError::MG0004DomainLevelInDevelopment);
811        }
812
813        // =========== Apply changes ==============
814        self.internal_migrate_or_create_batch(
815            &format!("phase 1 - schema attrs target {}", DOMAIN_TGT_LEVEL),
816            migration_data::dl14::phase_1_schema_attrs(),
817        )?;
818
819        self.internal_migrate_or_create_batch(
820            "phase 2 - schema classes",
821            migration_data::dl14::phase_2_schema_classes(),
822        )?;
823
824        // Reload for the new schema.
825        self.reload()?;
826
827        // Since we just loaded in a ton of schema, lets reindex it in case we added
828        // new indexes, or this is a bootstrap and we have no indexes yet.
829        self.reindex(false)?;
830
831        // Set Phase
832        // Indicate the schema is now ready, which allows dyngroups to work when they
833        // are created in the next phase of migrations.
834        self.set_phase(ServerPhase::SchemaReady);
835
836        self.internal_migrate_or_create_batch(
837            "phase 3 - key provider",
838            migration_data::dl14::phase_3_key_provider(),
839        )?;
840
841        // Reload for the new key providers
842        self.reload()?;
843
844        self.internal_migrate_or_create_batch(
845            "phase 4 - dl14 system entries",
846            migration_data::dl14::phase_4_system_entries(),
847        )?;
848
849        // Reload for the new system entries
850        self.reload()?;
851
852        // Domain info is now ready and reloaded, we can proceed.
853        self.set_phase(ServerPhase::DomainInfoReady);
854
855        // Bring up the IDM entries.
856        self.internal_migrate_or_create_batch(
857            "phase 5 - builtin admin entries",
858            migration_data::dl14::phase_5_builtin_admin_entries()?,
859        )?;
860
861        self.internal_migrate_or_create_batch(
862            "phase 6 - builtin not admin entries",
863            migration_data::dl14::phase_6_builtin_non_admin_entries()?,
864        )?;
865
866        self.internal_migrate_or_create_batch(
867            "phase 7 - builtin access control profiles",
868            migration_data::dl14::phase_7_builtin_access_control_profiles(),
869        )?;
870
871        self.internal_delete_batch(
872            "phase 8 - delete UUIDS",
873            migration_data::dl14::phase_8_delete_uuids(),
874        )?;
875
876        self.reload()?;
877
878        // Default PasswordChangedTime to UNIX_EPOCH
879        let filter = filter_all!(f_and!([
880            f_eq(Attribute::Class, EntryClass::Person.into()),
881            f_andnot(f_pres(Attribute::PasswordChangedTime)),
882        ]));
883        let modlist = ModifyList::new_purge_and_set(
884            Attribute::PasswordChangedTime,
885            Value::DateTime(time::OffsetDateTime::UNIX_EPOCH),
886        );
887        self.internal_modify(&filter, &modlist)?;
888
889        Ok(())
890    }
891
892    pub(crate) fn migrate_schema_1_11(&mut self) -> Result<(), OperationError> {
893        self.schema.extend_in_memory(
894            migration_data::dl15::phase_1_schema_attrs(),
895            migration_data::dl15::phase_2_schema_classes(),
896        )
897    }
898
899    /// Migration domain level 14 to 15 (1.11.0)
900    #[instrument(level = "info", skip_all)]
901    pub(crate) fn migrate_domain_1_10_to_1_11(&mut self) -> Result<(), OperationError> {
902        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_14 {
903            error!(
904                "Unable to raise domain level from {} to {}.",
905                DOMAIN_LEVEL_14, DOMAIN_LEVEL_1_11
906            );
907            return Err(OperationError::MG0004DomainLevelInDevelopment);
908        }
909
910        // =========== Apply changes ==============
911        self.migrate_schema_1_11()?;
912
913        // Reload for the new schema.
914        self.reload()?;
915
916        // Since we just loaded in a ton of schema, lets reindex it in case we added
917        // new indexes, or this is a bootstrap and we have no indexes yet.
918        self.reindex(false)?;
919
920        // Delete all existing DB contained schema.
921
922        let filter = filter!(f_and(vec![
923            f_eq(Attribute::Class, EntryClass::ClassType.into()),
924            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
925        ]));
926
927        self.internal_delete_if_exists(&filter)?;
928
929        // Set Phase
930        // Indicate the schema is now ready, which allows dyngroups to work when they
931        // are created in the next phase of migrations.
932        self.set_phase(ServerPhase::SchemaReady);
933
934        self.internal_migrate_or_create_batch(
935            "phase 3 - key provider",
936            migration_data::dl15::phase_3_key_provider(),
937        )?;
938
939        // Reload for the new key providers
940        self.reload()?;
941
942        self.internal_migrate_or_create_batch(
943            "phase 4 - dl15 system entries",
944            migration_data::dl15::phase_4_system_entries(),
945        )?;
946
947        // Reload for the new system entries
948        self.reload()?;
949
950        // Domain info is now ready and reloaded, we can proceed.
951        self.set_phase(ServerPhase::DomainInfoReady);
952
953        // Bring up the IDM entries.
954        self.internal_migrate_or_create_batch(
955            "phase 5 - builtin admin entries",
956            migration_data::dl15::phase_5_builtin_admin_entries()?,
957        )?;
958
959        self.internal_migrate_or_create_batch(
960            "phase 6 - builtin not admin entries",
961            migration_data::dl15::phase_6_builtin_non_admin_entries()?,
962        )?;
963
964        self.internal_migrate_or_create_batch(
965            "phase 7 - builtin access control profiles",
966            migration_data::dl15::phase_7_builtin_access_control_profiles(),
967        )?;
968
969        self.internal_delete_batch(
970            "phase 8 - delete UUIDS",
971            migration_data::dl15::phase_8_delete_uuids(),
972        )?;
973
974        self.reload()?;
975
976        // Default PasswordChangedTime to UNIX_EPOCH
977        let filter = filter_all!(f_and!([
978            f_eq(Attribute::Class, EntryClass::Person.into()),
979            f_andnot(f_pres(Attribute::PasswordChangedTime)),
980        ]));
981        let modlist = ModifyList::new_purge_and_set(
982            Attribute::PasswordChangedTime,
983            Value::DateTime(time::OffsetDateTime::UNIX_EPOCH),
984        );
985        self.internal_modify(&filter, &modlist)?;
986
987        Ok(())
988    }
989
990    /// Migration domain level 1.11 to 1.12
991    #[instrument(level = "info", skip_all)]
992    pub(crate) fn migrate_domain_1_11_to_1_12(&mut self) -> Result<(), OperationError> {
993        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_1_11 {
994            error!("Unable to raise domain level from 15 to 16.");
995            return Err(OperationError::MG0004DomainLevelInDevelopment);
996        }
997
998        Ok(())
999    }
1000
1001    #[instrument(level = "info", skip_all)]
1002    pub(crate) fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
1003        debug!("initialise_schema_core -> start ...");
1004        // Load in all the "core" schema, that we already have in "memory".
1005        let entries = self.schema.to_entries();
1006
1007        // admin_debug!("Dumping schemas: {:?}", entries);
1008
1009        // internal_migrate_or_create.
1010        let r: Result<_, _> = entries.into_iter().try_for_each(|e| {
1011            trace!(?e, "init schema entry");
1012            self.internal_migrate_or_create(e)
1013        });
1014        if r.is_ok() {
1015            debug!("initialise_schema_core -> Ok!");
1016        } else {
1017            error!(?r, "initialise_schema_core -> Error");
1018        }
1019        // why do we have error handling if it's always supposed to be `Ok`?
1020        debug_assert!(r.is_ok());
1021        r
1022    }
1023}
1024
1025impl QueryServerReadTransaction<'_> {
1026    /// Retrieve the domain info of this server
1027    pub fn domain_upgrade_check(
1028        &mut self,
1029    ) -> Result<ProtoDomainUpgradeCheckReport, OperationError> {
1030        let d_info = &self.d_info;
1031
1032        let name = d_info.d_name.clone();
1033        let uuid = d_info.d_uuid;
1034        let current_level = d_info.d_vers;
1035        let upgrade_level = DOMAIN_TGT_NEXT_LEVEL;
1036
1037        let mut report_items = Vec::with_capacity(1);
1038
1039        if current_level <= DOMAIN_LEVEL_7 && upgrade_level >= DOMAIN_LEVEL_8 {
1040            let item = self
1041                .domain_upgrade_check_7_to_8_security_keys()
1042                .map_err(|err| {
1043                    error!(
1044                        ?err,
1045                        "Failed to perform domain upgrade check 7 to 8 - security-keys"
1046                    );
1047                    err
1048                })?;
1049            report_items.push(item);
1050
1051            let item = self
1052                .domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri()
1053                .map_err(|err| {
1054                    error!(
1055                        ?err,
1056                        "Failed to perform domain upgrade check 7 to 8 - oauth2-strict-redirect_uri"
1057                    );
1058                    err
1059                })?;
1060            report_items.push(item);
1061        }
1062
1063        Ok(ProtoDomainUpgradeCheckReport {
1064            name,
1065            uuid,
1066            current_level,
1067            upgrade_level,
1068            report_items,
1069        })
1070    }
1071
1072    pub(crate) fn domain_upgrade_check_7_to_8_security_keys(
1073        &mut self,
1074    ) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
1075        let filter = filter!(f_and!([
1076            f_eq(Attribute::Class, EntryClass::Account.into()),
1077            f_pres(Attribute::PrimaryCredential),
1078        ]));
1079
1080        let results = self.internal_search(filter)?;
1081
1082        let affected_entries = results
1083            .into_iter()
1084            .filter_map(|entry| {
1085                if entry
1086                    .get_ava_single_credential(Attribute::PrimaryCredential)
1087                    .map(|cred| cred.has_securitykey())
1088                    .unwrap_or_default()
1089                {
1090                    Some(entry.get_display_id())
1091                } else {
1092                    None
1093                }
1094            })
1095            .collect::<Vec<_>>();
1096
1097        let status = if affected_entries.is_empty() {
1098            ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys
1099        } else {
1100            ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys
1101        };
1102
1103        Ok(ProtoDomainUpgradeCheckItem {
1104            status,
1105            from_level: DOMAIN_LEVEL_7,
1106            to_level: DOMAIN_LEVEL_8,
1107            affected_entries,
1108        })
1109    }
1110
1111    pub(crate) fn domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri(
1112        &mut self,
1113    ) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
1114        let filter = filter!(f_and!([
1115            f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
1116            f_andnot(f_pres(Attribute::OAuth2StrictRedirectUri)),
1117        ]));
1118
1119        let results = self.internal_search(filter)?;
1120
1121        let affected_entries = results
1122            .into_iter()
1123            .map(|entry| entry.get_display_id())
1124            .collect::<Vec<_>>();
1125
1126        let status = if affected_entries.is_empty() {
1127            ProtoDomainUpgradeCheckStatus::Pass7To8Oauth2StrictRedirectUri
1128        } else {
1129            ProtoDomainUpgradeCheckStatus::Fail7To8Oauth2StrictRedirectUri
1130        };
1131
1132        Ok(ProtoDomainUpgradeCheckItem {
1133            status,
1134            from_level: DOMAIN_LEVEL_7,
1135            to_level: DOMAIN_LEVEL_8,
1136            affected_entries,
1137        })
1138    }
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143    // use super::{ProtoDomainUpgradeCheckItem, ProtoDomainUpgradeCheckStatus};
1144    use crate::prelude::*;
1145    use crate::value::CredentialType;
1146    use crate::valueset::ValueSetCredentialType;
1147
1148    #[qs_test]
1149    async fn test_init_idempotent_schema_core(server: &QueryServer) {
1150        {
1151            // Setup and abort.
1152            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
1153            assert!(server_txn.initialise_schema_core().is_ok());
1154        }
1155        {
1156            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
1157            assert!(server_txn.initialise_schema_core().is_ok());
1158            assert!(server_txn.initialise_schema_core().is_ok());
1159            assert!(server_txn.commit().is_ok());
1160        }
1161        {
1162            // Now do it again in a new txn, but abort
1163            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
1164            assert!(server_txn.initialise_schema_core().is_ok());
1165        }
1166        {
1167            // Now do it again in a new txn.
1168            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
1169            assert!(server_txn.initialise_schema_core().is_ok());
1170            assert!(server_txn.commit().is_ok());
1171        }
1172    }
1173
1174    /// This test is for ongoing/longterm checks over the previous to current version.
1175    /// This is in contrast to the specific version checks below that are often to
1176    /// test a version to version migration.
1177    #[qs_test(domain_level=DOMAIN_PREVIOUS_TGT_LEVEL)]
1178    async fn test_migrations_dl_previous_to_dl_target(server: &QueryServer) {
1179        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1180
1181        let db_domain_version = write_txn
1182            .internal_search_uuid(UUID_DOMAIN_INFO)
1183            .expect("unable to access domain entry")
1184            .get_ava_single_uint32(Attribute::Version)
1185            .expect("Attribute Version not present");
1186
1187        assert_eq!(db_domain_version, DOMAIN_PREVIOUS_TGT_LEVEL);
1188
1189        // == SETUP ==
1190
1191        // Add a member to a group - it should not be removed.
1192        // Remove a default member from a group - it should be returned.
1193        let modlist = ModifyList::new_set(
1194            Attribute::Member,
1195            // This achieves both because this removes IDM_ADMIN from the group
1196            // while setting only anon as a member.
1197            ValueSetRefer::new(UUID_ANONYMOUS),
1198        );
1199        write_txn
1200            .internal_modify_uuid(UUID_IDM_ADMINS, &modlist)
1201            .expect("Unable to modify CredentialTypeMinimum");
1202
1203        // Remove a group from an object that is "create once".  It should not
1204        // be re-added.
1205        let modlist = ModifyList::new_purge(Attribute::Member);
1206        write_txn
1207            .internal_modify_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE, &modlist)
1208            .expect("Unable to remove idm_all_persons from self-write");
1209
1210        // Change default account policy - it should not be reverted.
1211        let modlist = ModifyList::new_set(
1212            Attribute::CredentialTypeMinimum,
1213            ValueSetCredentialType::new(CredentialType::Any),
1214        );
1215        write_txn
1216            .internal_modify_uuid(UUID_IDM_ALL_PERSONS, &modlist)
1217            .expect("Unable to modify CredentialTypeMinimum");
1218
1219        write_txn.commit().expect("Unable to commit");
1220
1221        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1222
1223        // == Increase the version ==
1224        write_txn
1225            .internal_apply_domain_migration(DOMAIN_TGT_LEVEL)
1226            .expect("Unable to set domain level");
1227
1228        // post migration verification.
1229        // Check that our group is as we left it
1230        let idm_admins_entry = write_txn
1231            .internal_search_uuid(UUID_IDM_ADMINS)
1232            .expect("Unable to retrieve all persons");
1233
1234        let members = idm_admins_entry
1235            .get_ava_refer(Attribute::Member)
1236            .expect("No members present");
1237
1238        // Still present
1239        assert!(members.contains(&UUID_ANONYMOUS));
1240        // Was reverted
1241        assert!(members.contains(&UUID_IDM_ADMIN));
1242
1243        // Check that self-write still doesn't have all persons.
1244        let idm_people_self_name_write_entry = write_txn
1245            .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE)
1246            .expect("Unable to retrieve all persons");
1247
1248        let members = idm_people_self_name_write_entry.get_ava_refer(Attribute::Member);
1249
1250        // There are no members!
1251        assert!(members.is_none());
1252
1253        // Check that the account policy did not revert.
1254        let all_persons_entry = write_txn
1255            .internal_search_uuid(UUID_IDM_ALL_PERSONS)
1256            .expect("Unable to retrieve all persons");
1257
1258        assert_eq!(
1259            all_persons_entry.get_ava_single_credential_type(Attribute::CredentialTypeMinimum),
1260            Some(CredentialType::Any)
1261        );
1262
1263        write_txn.commit().expect("Unable to commit");
1264    }
1265
1266    #[qs_test(domain_level=DOMAIN_TGT_LEVEL)]
1267    async fn test_migrations_prevent_downgrades(server: &QueryServer) {
1268        let curtime = duration_from_epoch_now();
1269
1270        let mut write_txn = server.write(curtime).await.unwrap();
1271
1272        let db_domain_version = write_txn
1273            .internal_search_uuid(UUID_DOMAIN_INFO)
1274            .expect("unable to access domain entry")
1275            .get_ava_single_uint32(Attribute::Version)
1276            .expect("Attribute Version not present");
1277
1278        assert_eq!(db_domain_version, DOMAIN_TGT_LEVEL);
1279
1280        drop(write_txn);
1281
1282        // MUST NOT SUCCEED.
1283        let err = server
1284            .initialise_helper(curtime, DOMAIN_PREVIOUS_TGT_LEVEL)
1285            .await
1286            .expect_err("Domain level was lowered!!!!");
1287
1288        assert_eq!(err, OperationError::MG0010DowngradeNotAllowed);
1289    }
1290
1291    #[qs_test(domain_level=DOMAIN_MIGRATION_FROM_INVALID)]
1292    async fn test_migrations_prevent_skips(server: &QueryServer) {
1293        let curtime = duration_from_epoch_now();
1294
1295        let mut write_txn = server.write(curtime).await.unwrap();
1296
1297        let db_domain_version = write_txn
1298            .internal_search_uuid(UUID_DOMAIN_INFO)
1299            .expect("unable to access domain entry")
1300            .get_ava_single_uint32(Attribute::Version)
1301            .expect("Attribute Version not present");
1302
1303        assert_eq!(db_domain_version, DOMAIN_MIGRATION_FROM_INVALID);
1304
1305        drop(write_txn);
1306
1307        // MUST NOT SUCCEED.
1308        let err = server
1309            .initialise_helper(curtime, DOMAIN_TGT_LEVEL)
1310            .await
1311            .expect_err("Migration went ahead!!!!");
1312
1313        assert_eq!(err, OperationError::MG0008SkipUpgradeAttempted);
1314    }
1315
1316    #[qs_test(domain_level=DOMAIN_MIGRATION_FROM_MIN)]
1317    async fn test_migrations_skip_valid(server: &QueryServer) {
1318        let curtime = duration_from_epoch_now();
1319        // This is a smoke test that X -> Z migrations work for some range. This doesn't
1320        // absolve us of the need to write more detailed migration tests.
1321        let mut write_txn = server.write(curtime).await.unwrap();
1322
1323        let db_domain_version = write_txn
1324            .internal_search_uuid(UUID_DOMAIN_INFO)
1325            .expect("unable to access domain entry")
1326            .get_ava_single_uint32(Attribute::Version)
1327            .expect("Attribute Version not present");
1328
1329        assert_eq!(db_domain_version, DOMAIN_MIGRATION_FROM_MIN);
1330
1331        drop(write_txn);
1332
1333        // MUST SUCCEED.
1334        server
1335            .initialise_helper(curtime, DOMAIN_TGT_LEVEL)
1336            .await
1337            .expect("Migration failed!!!!")
1338    }
1339
1340    #[qs_test(domain_level=DOMAIN_LEVEL_10)]
1341    async fn test_migrations_dl10_dl11(server: &QueryServer) {
1342        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1343
1344        let db_domain_version = write_txn
1345            .internal_search_uuid(UUID_DOMAIN_INFO)
1346            .expect("unable to access domain entry")
1347            .get_ava_single_uint32(Attribute::Version)
1348            .expect("Attribute Version not present");
1349
1350        assert_eq!(db_domain_version, DOMAIN_LEVEL_10);
1351
1352        write_txn.commit().expect("Unable to commit");
1353
1354        // == pre migration verification. ==
1355        // check we currently would fail a migration.
1356
1357        // let mut read_txn = server.read().await.unwrap();
1358        // drop(read_txn);
1359
1360        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1361
1362        // Fix any issues
1363
1364        // == Increase the version ==
1365        write_txn
1366            .internal_apply_domain_migration(DOMAIN_LEVEL_11)
1367            .expect("Unable to set domain level to version 11");
1368
1369        // post migration verification.
1370
1371        write_txn.commit().expect("Unable to commit");
1372    }
1373
1374    #[qs_test(domain_level=DOMAIN_LEVEL_11)]
1375    async fn test_migrations_dl11_dl12(server: &QueryServer) {
1376        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1377
1378        let db_domain_version = write_txn
1379            .internal_search_uuid(UUID_DOMAIN_INFO)
1380            .expect("unable to access domain entry")
1381            .get_ava_single_uint32(Attribute::Version)
1382            .expect("Attribute Version not present");
1383
1384        assert_eq!(db_domain_version, DOMAIN_LEVEL_11);
1385
1386        // Make a new person.
1387        let tuuid = Uuid::new_v4();
1388        let e1 = entry_init!(
1389            (Attribute::Class, EntryClass::Object.to_value()),
1390            (Attribute::Class, EntryClass::Person.to_value()),
1391            (Attribute::Class, EntryClass::Account.to_value()),
1392            (Attribute::Name, Value::new_iname("testperson1")),
1393            (Attribute::Uuid, Value::Uuid(tuuid)),
1394            (Attribute::Description, Value::new_utf8s("testperson1")),
1395            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1396        );
1397
1398        write_txn
1399            .internal_create(vec![e1])
1400            .expect("Unable to create user");
1401
1402        let user = write_txn
1403            .internal_search_uuid(tuuid)
1404            .expect("Unable to load user");
1405
1406        // They still have an id verification key
1407        assert!(user.get_ava_set(Attribute::IdVerificationEcKey).is_some());
1408
1409        write_txn.commit().expect("Unable to commit");
1410
1411        // == pre migration verification. ==
1412        // check we currently would fail a migration.
1413
1414        // let mut read_txn = server.read().await.unwrap();
1415        // drop(read_txn);
1416
1417        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1418
1419        // Fix any issues
1420
1421        // == Increase the version ==
1422        write_txn
1423            .internal_apply_domain_migration(DOMAIN_LEVEL_12)
1424            .expect("Unable to set domain level to version 12");
1425
1426        // post migration verification.
1427        let user = write_txn
1428            .internal_search_uuid(tuuid)
1429            .expect("Unable to load user");
1430
1431        // The key has been removed.
1432        assert!(user.get_ava_set(Attribute::IdVerificationEcKey).is_none());
1433
1434        // New users don't get a key
1435        let t2uuid = Uuid::new_v4();
1436        let e2 = entry_init!(
1437            (Attribute::Class, EntryClass::Object.to_value()),
1438            (Attribute::Class, EntryClass::Person.to_value()),
1439            (Attribute::Class, EntryClass::Account.to_value()),
1440            (Attribute::Name, Value::new_iname("testperson2")),
1441            (Attribute::Uuid, Value::Uuid(t2uuid)),
1442            (Attribute::Description, Value::new_utf8s("testperson2")),
1443            (Attribute::DisplayName, Value::new_utf8s("testperson2"))
1444        );
1445
1446        write_txn
1447            .internal_create(vec![e2])
1448            .expect("Unable to create user");
1449
1450        let user = write_txn
1451            .internal_search_uuid(t2uuid)
1452            .expect("Unable to load user");
1453
1454        // No key!
1455        assert!(user.get_ava_set(Attribute::IdVerificationEcKey).is_none());
1456
1457        write_txn.commit().expect("Unable to commit");
1458    }
1459
1460    #[qs_test(domain_level=DOMAIN_LEVEL_12)]
1461    async fn test_migrations_dl12_dl13(server: &QueryServer) {
1462        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1463
1464        let db_domain_version = write_txn
1465            .internal_search_uuid(UUID_DOMAIN_INFO)
1466            .expect("unable to access domain entry")
1467            .get_ava_single_uint32(Attribute::Version)
1468            .expect("Attribute Version not present");
1469
1470        assert_eq!(db_domain_version, DOMAIN_LEVEL_12);
1471
1472        write_txn.commit().expect("Unable to commit");
1473
1474        // == pre migration verification. ==
1475        // check we currently would fail a migration.
1476
1477        // let mut read_txn = server.read().await.unwrap();
1478        // drop(read_txn);
1479
1480        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1481
1482        // Fix any issues
1483
1484        // == Increase the version ==
1485        write_txn
1486            .internal_apply_domain_migration(DOMAIN_LEVEL_13)
1487            .expect("Unable to set domain level to version 13");
1488
1489        // post migration verification.
1490
1491        write_txn.commit().expect("Unable to commit");
1492    }
1493
1494    #[qs_test(domain_level=DOMAIN_LEVEL_13)]
1495    async fn test_migrations_dl13_dl14(server: &QueryServer) {
1496        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1497
1498        let db_domain_version = write_txn
1499            .internal_search_uuid(UUID_DOMAIN_INFO)
1500            .expect("unable to access domain entry")
1501            .get_ava_single_uint32(Attribute::Version)
1502            .expect("Attribute Version not present");
1503
1504        assert_eq!(db_domain_version, DOMAIN_LEVEL_13);
1505
1506        // Create a person without pwd_changed_time
1507        let tuuid = Uuid::new_v4();
1508        let e1 = entry_init!(
1509            (Attribute::Class, EntryClass::Object.to_value()),
1510            (Attribute::Class, EntryClass::Person.to_value()),
1511            (Attribute::Class, EntryClass::Account.to_value()),
1512            (Attribute::Name, Value::new_iname("testperson1")),
1513            (Attribute::Uuid, Value::Uuid(tuuid)),
1514            (Attribute::Description, Value::new_utf8s("testperson1")),
1515            (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1516        );
1517
1518        write_txn
1519            .internal_create(vec![e1])
1520            .expect("Unable to create test person");
1521
1522        let user = write_txn
1523            .internal_search_uuid(tuuid)
1524            .expect("Unable to load test person");
1525
1526        // sanity check
1527        assert!(user
1528            .get_ava_single_datetime(Attribute::PasswordChangedTime)
1529            .is_none());
1530
1531        write_txn.commit().expect("Unable to commit");
1532
1533        // == pre migration verification. ==
1534        // check we currently would fail a migration.
1535
1536        // let mut read_txn = server.read().await.unwrap();
1537        // drop(read_txn);
1538
1539        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1540
1541        // Fix any issues
1542
1543        // == Increase the version ==
1544        write_txn
1545            .internal_apply_domain_migration(DOMAIN_LEVEL_14)
1546            .expect("Unable to set domain level to version 14");
1547
1548        // post migration verification.
1549        // pwd_changed_time should be defaulted to UNIX_EPOCH
1550        let user = write_txn
1551            .internal_search_uuid(tuuid)
1552            .expect("Unable to load test person after migration");
1553
1554        let pwd_changed = user
1555            .get_ava_single_datetime(Attribute::PasswordChangedTime)
1556            .expect("PasswordChangedTime should be set after DL13->DL14 migration");
1557
1558        assert_eq!(pwd_changed, time::OffsetDateTime::UNIX_EPOCH);
1559
1560        write_txn.commit().expect("Unable to commit");
1561    }
1562
1563    #[qs_test(domain_level=DOMAIN_LEVEL_14)]
1564    async fn test_migrations_dl14_dl1_11(server: &QueryServer) {
1565        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1566
1567        let db_domain_version = write_txn
1568            .internal_search_uuid(UUID_DOMAIN_INFO)
1569            .expect("unable to access domain entry")
1570            .get_ava_single_uint32(Attribute::Version)
1571            .expect("Attribute Version not present");
1572
1573        assert_eq!(db_domain_version, DOMAIN_LEVEL_14);
1574
1575        write_txn.commit().expect("Unable to commit");
1576
1577        // == pre migration verification. ==
1578        // check we currently would fail a migration.
1579
1580        // let mut read_txn = server.read().await.unwrap();
1581        // drop(read_txn);
1582
1583        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
1584
1585        // Fix any issues
1586
1587        // == Increase the version ==
1588        write_txn
1589            .internal_apply_domain_migration(DOMAIN_LEVEL_1_11)
1590            .expect("Unable to set domain level to version 1_11");
1591
1592        // post migration verification.
1593
1594        // Assert all lingering schema db entries are removed.
1595
1596        let filter = filter!(f_and(vec![
1597            f_eq(Attribute::Class, EntryClass::ClassType.into()),
1598            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1599        ]));
1600
1601        let entries_remain = write_txn.internal_exists(&filter).unwrap();
1602        assert!(!entries_remain);
1603
1604        write_txn.commit().expect("Unable to commit");
1605    }
1606}