gea-program 0.55.0__tar.gz → 0.56.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gea_program-0.55.0/gea_program.egg-info → gea_program-0.56.0}/PKG-INFO +20 -1
- {gea_program-0.55.0 → gea_program-0.56.0}/README.md +19 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/__init__.py +1 -1
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/__main__.py +65 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/acceptance_tests.py +200 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/doctor.py +1 -1
- gea_program-0.56.0/gea/help/company-sign-in.md +19 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/helplib.py +1 -0
- gea_program-0.56.0/gea/oidc.py +379 -0
- gea_program-0.56.0/gea/oidc_mock.py +135 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/service.py +122 -4
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/web/app.html +21 -5
- {gea_program-0.55.0 → gea_program-0.56.0/gea_program.egg-info}/PKG-INFO +20 -1
- {gea_program-0.55.0 → gea_program-0.56.0}/gea_program.egg-info/SOURCES.txt +3 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/pyproject.toml +1 -1
- {gea_program-0.55.0 → gea_program-0.56.0}/LICENSE +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/alerts.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/backup.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/bench.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/blind_harness.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog_map.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/catalog_register.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/certificates.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/cli.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/client_reports.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/collision.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/config_versioning.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/conformance.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/control_room.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/correlation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/dashboard.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/declarations.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/decline.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/deviation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/dlis.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/dnp3.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/dossier.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/dossier_verify.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/drill_string.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/earth_model.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/economics.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/ed25519.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/edr.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/elevation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/etp.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/example_register_map.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/fat_sat.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/files.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/follower.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/forward_model.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/frac_hits.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/friction_priors.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/gamma.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/geodesy.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/geomag.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/hartip.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/alarms.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/alerts.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/backup.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/catalog.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/charts.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/collision.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/conformance.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/control-room.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/data-in.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/declare.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/decline.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/dlis.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/doctor.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/dossier.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/drift.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/drillstring.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/economics.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/edr.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/etp.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/files.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/frac-hits.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/historian.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/https.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/instruments.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/interval.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/invoice-check.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/map.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/month-end.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/notifications.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/opseis.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/osdu.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/pad-summary.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/patches.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/penetrator.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/plan.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/pore-pressure.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/quality.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/report-history.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/rig-time.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/seismic.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/setup.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/simulation.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/site.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/sites.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/slip.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/soak.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/sra.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/start.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/structure.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/tools.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/torque-drag.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/transients.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/vibration.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/well-report.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/help/well3d.md +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/housekeeping.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/interval.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/invoice_check.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/jobs.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/live_ports.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/loadtest.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/modbus.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/model_card.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/month_end.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/notify.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/op_seismicity.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/opcua_port.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/operator_app.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/osdu.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/pad_summary.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/patches.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/pdf_report.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/penetrator.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/permits.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/piwebapi.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/pore_pressure.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/ports.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/ppdm.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/project.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/quakeml.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reconciler.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/WMM2025.COF +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/eia_prices.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/iscwsa_rev4_example.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/iscwsa_rev5_example.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/mahoning_example.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/mud_motor_specs.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/owsg_tool_models.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/penetrator_ratings.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/seismicity_protocols.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_3d.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_surfaces.json.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/report_history.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/rig_state.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/sample_record.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/sbom.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seedlink.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/segy.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_array.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_film.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_response.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_track.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/seismic_watch.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/series_cache.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/service_life.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/shapefile.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/shell.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/shut_in.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/simulation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/site_map.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/sla_report.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/slip.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/soak.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/sra.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/store_forward.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/strata_join.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/structure.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/supervisor.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/survey_view.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/telemetry.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/tls.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/tool_catalog.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/tool_library.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/torque_drag.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/transient.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/vibration.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/well3d.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/well_assembler.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/well_report.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/wellplan.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/wellpos.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/wits0.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/witsml.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/workspace.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea/xlsx_read.py +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/setup.cfg +0 -0
- {gea_program-0.55.0 → gea_program-0.56.0}/tests/test_acceptance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gea-program
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.56.0
|
|
4
4
|
Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
|
|
5
5
|
Author: Daniel T. Murphy
|
|
6
6
|
License: MPL-2.0
|
|
@@ -1272,6 +1272,25 @@ Or Administration -> **HTTPS** in the page (download the certificate, not
|
|
|
1272
1272
|
the key, for the site's computers to trust; compare the SHA-256 shown).
|
|
1273
1273
|
`gea help https`.
|
|
1274
1274
|
|
|
1275
|
+
### Company sign-in
|
|
1276
|
+
|
|
1277
|
+
People sign in with the account the company already manages - Microsoft
|
|
1278
|
+
Entra ID, Okta, Google Workspace, Ping, Keycloak, AD FS - by OpenID Connect
|
|
1279
|
+
(the authorization code flow with PKCE), so the company's multi-factor,
|
|
1280
|
+
conditional access and leavers apply. The ID token is checked with the
|
|
1281
|
+
standard library alone (its RS256 signature against the provider's
|
|
1282
|
+
published keys, the issuer, the audience, the times, the nonce); the role in
|
|
1283
|
+
GEA is the highest any of the person's app roles or groups maps to. Local
|
|
1284
|
+
accounts stay, for the first administrator and for when the provider cannot
|
|
1285
|
+
be reached. HTTPS comes first (above).
|
|
1286
|
+
|
|
1287
|
+
```
|
|
1288
|
+
gea oidc --workspace C:\site --issuer https://login.microsoftonline.com/3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64/v2.0 --client-id a41c7e92-0d3b-4f6a-9e85-2b6d1c4f7a30 --redirect-uri https://gea-pad3.example.local:8765/api/oidc/callback --secret-from-env GEA_OIDC_SECRET --map GEA.Operator=operator --map GEA.Admin=admin --test
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
Or Administration -> **Company sign-in**. `gea help company-sign-in` has the
|
|
1292
|
+
Entra ID app registration step by step.
|
|
1293
|
+
|
|
1275
1294
|
### Before a site goes live
|
|
1276
1295
|
|
|
1277
1296
|
`deploy/README.md` is the checklist: HTTPS from the service itself (above),
|
|
@@ -1233,6 +1233,25 @@ Or Administration -> **HTTPS** in the page (download the certificate, not
|
|
|
1233
1233
|
the key, for the site's computers to trust; compare the SHA-256 shown).
|
|
1234
1234
|
`gea help https`.
|
|
1235
1235
|
|
|
1236
|
+
### Company sign-in
|
|
1237
|
+
|
|
1238
|
+
People sign in with the account the company already manages - Microsoft
|
|
1239
|
+
Entra ID, Okta, Google Workspace, Ping, Keycloak, AD FS - by OpenID Connect
|
|
1240
|
+
(the authorization code flow with PKCE), so the company's multi-factor,
|
|
1241
|
+
conditional access and leavers apply. The ID token is checked with the
|
|
1242
|
+
standard library alone (its RS256 signature against the provider's
|
|
1243
|
+
published keys, the issuer, the audience, the times, the nonce); the role in
|
|
1244
|
+
GEA is the highest any of the person's app roles or groups maps to. Local
|
|
1245
|
+
accounts stay, for the first administrator and for when the provider cannot
|
|
1246
|
+
be reached. HTTPS comes first (above).
|
|
1247
|
+
|
|
1248
|
+
```
|
|
1249
|
+
gea oidc --workspace C:\site --issuer https://login.microsoftonline.com/3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64/v2.0 --client-id a41c7e92-0d3b-4f6a-9e85-2b6d1c4f7a30 --redirect-uri https://gea-pad3.example.local:8765/api/oidc/callback --secret-from-env GEA_OIDC_SECRET --map GEA.Operator=operator --map GEA.Admin=admin --test
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
Or Administration -> **Company sign-in**. `gea help company-sign-in` has the
|
|
1253
|
+
Entra ID app registration step by step.
|
|
1254
|
+
|
|
1236
1255
|
### Before a site goes live
|
|
1237
1256
|
|
|
1238
1257
|
`deploy/README.md` is the checklist: HTTPS from the service itself (above),
|
|
@@ -776,6 +776,24 @@ def main(argv=None) -> int:
|
|
|
776
776
|
p_tl.add_argument("--actor", type=str, default="cli")
|
|
777
777
|
p_tl.add_argument("--json", action="store_true")
|
|
778
778
|
|
|
779
|
+
p_oc = sub.add_parser("oidc", help="company sign-in (OpenID Connect: Entra ID, Okta, Google, Ping, Keycloak, AD FS): set it, test the provider, remove it; with no option, how it stands")
|
|
780
|
+
p_oc.add_argument("--workspace", type=str, required=True)
|
|
781
|
+
p_oc.add_argument("--issuer", type=str, default=None, help="Entra ID: https://login.microsoftonline.com/3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64/v2.0")
|
|
782
|
+
p_oc.add_argument("--client-id", type=str, default=None)
|
|
783
|
+
p_oc.add_argument("--redirect-uri", type=str, default=None, help="https://gea-pad3.example.local:8765/api/oidc/callback, registered with the provider exactly")
|
|
784
|
+
p_oc.add_argument("--secret-from-env", type=str, default=None, help="read the client secret from this environment variable now and keep it in keys\\oidc_client_secret.txt")
|
|
785
|
+
p_oc.add_argument("--secret-env", type=str, default=None, help="or: the service reads the client secret from this environment variable at each sign-in (nothing kept)")
|
|
786
|
+
p_oc.add_argument("--role-claim", type=str, default=None, help="roles (Entra ID app roles, the default) or groups, or any claim")
|
|
787
|
+
p_oc.add_argument("--map", action="append", default=[], help="VALUE=ROLE, e.g. GEA.Admin=admin (repeat)")
|
|
788
|
+
p_oc.add_argument("--default-role", type=str, default=None, help="the role of a person with no mapped value; 'none' (the default) means no sign-in")
|
|
789
|
+
p_oc.add_argument("--name-claim", type=str, default=None, help="the claim that names the person (preferred_username by default)")
|
|
790
|
+
p_oc.add_argument("--label", type=str, default=None, help="the sign-in button's words")
|
|
791
|
+
p_oc.add_argument("--pause", action="store_true", help="keep the settings, stop company sign-in")
|
|
792
|
+
p_oc.add_argument("--test", action="store_true", help="read the provider's discovery document and keys now")
|
|
793
|
+
p_oc.add_argument("--off", action="store_true", help="remove company sign-in (local accounts are unchanged)")
|
|
794
|
+
p_oc.add_argument("--actor", type=str, default="cli")
|
|
795
|
+
p_oc.add_argument("--json", action="store_true")
|
|
796
|
+
|
|
779
797
|
p_hk = sub.add_parser("housekeeping", help="rotate the append-only logs, prune finished job folders and old live recordings (dry run unless --apply)")
|
|
780
798
|
p_hk.add_argument("--workspace", type=str, required=True)
|
|
781
799
|
p_hk.add_argument("--apply", action="store_true")
|
|
@@ -2616,6 +2634,53 @@ def main(argv=None) -> int:
|
|
|
2616
2634
|
raise SystemExit(f"pore-pressure: {e.args[0] if e.args else e}")
|
|
2617
2635
|
print(_json.dumps(r, default=str) if a.json else _PPF.text(r))
|
|
2618
2636
|
return 0
|
|
2637
|
+
elif a.cmd == "oidc":
|
|
2638
|
+
import json as _json
|
|
2639
|
+
from . import oidc as _OI
|
|
2640
|
+
from .workspace import Workspace, WorkspaceError
|
|
2641
|
+
try:
|
|
2642
|
+
ws = Workspace(a.workspace)
|
|
2643
|
+
changing = any(x is not None for x in (a.issuer, a.client_id, a.redirect_uri, a.secret_from_env, a.secret_env, a.role_claim,
|
|
2644
|
+
a.default_role, a.name_claim, a.label)) or a.map or a.pause
|
|
2645
|
+
if a.off:
|
|
2646
|
+
r = _OI.turn_off(ws, a.actor)
|
|
2647
|
+
elif changing:
|
|
2648
|
+
cur = _OI.settings(ws) or {}
|
|
2649
|
+
s = {k: cur.get(k, v) for k, v in _OI.DEFAULTS.items()}
|
|
2650
|
+
for k, v in (("issuer", a.issuer), ("client_id", a.client_id), ("redirect_uri", a.redirect_uri), ("role_claim", a.role_claim),
|
|
2651
|
+
("name_claim", a.name_claim), ("label", a.label), ("client_secret_env", a.secret_env)):
|
|
2652
|
+
if v is not None:
|
|
2653
|
+
s[k] = v
|
|
2654
|
+
if a.default_role is not None:
|
|
2655
|
+
s["default_role"] = None if a.default_role.lower() in ("", "none") else a.default_role
|
|
2656
|
+
if a.map:
|
|
2657
|
+
m = {}
|
|
2658
|
+
for x in a.map:
|
|
2659
|
+
v_, _, r_ = x.rpartition("=")
|
|
2660
|
+
if not v_:
|
|
2661
|
+
raise ValueError(f"--map takes VALUE=ROLE, not {x!r}")
|
|
2662
|
+
m[v_.strip()] = r_.strip()
|
|
2663
|
+
s["role_map"] = m
|
|
2664
|
+
if a.secret_from_env:
|
|
2665
|
+
s["client_secret_env"] = ""
|
|
2666
|
+
s["enabled"] = not a.pause
|
|
2667
|
+
sec = None
|
|
2668
|
+
if a.secret_from_env:
|
|
2669
|
+
sec = os.environ.get(a.secret_from_env, "")
|
|
2670
|
+
if not sec:
|
|
2671
|
+
raise ValueError(f"the environment variable {a.secret_from_env} is empty")
|
|
2672
|
+
r = _OI.configure(ws, a.actor, s, sec)
|
|
2673
|
+
else:
|
|
2674
|
+
r = _OI.status(ws)
|
|
2675
|
+
if a.test:
|
|
2676
|
+
r = {**r, "test": _OI.test(ws)}
|
|
2677
|
+
except (KeyError, ValueError, OSError, WorkspaceError) as e:
|
|
2678
|
+
raise SystemExit(f"oidc: {e.args[0] if e.args else e}")
|
|
2679
|
+
if a.json:
|
|
2680
|
+
print(_json.dumps(r, default=str))
|
|
2681
|
+
else:
|
|
2682
|
+
print(_OI.text(r))
|
|
2683
|
+
return 0
|
|
2619
2684
|
elif a.cmd == "tls":
|
|
2620
2685
|
import json as _json
|
|
2621
2686
|
from . import tls as _TLS
|
|
@@ -12461,6 +12461,205 @@ def section_cx_https(tmp: str) -> None:
|
|
|
12461
12461
|
"no dossier carries the key; the help page" + (f" - failed: {failed4}" if failed4 else ""))
|
|
12462
12462
|
|
|
12463
12463
|
|
|
12464
|
+
def section_cy_company_sign_in(tmp: str) -> None:
|
|
12465
|
+
"""Section CY - company sign-in (OpenID Connect): against a stand-in provider over HTTPS, the whole browser round
|
|
12466
|
+
trip - the start, the provider, the code exchanged with PKCE and the secret, the ID token checked, the role from the
|
|
12467
|
+
claims, a session - and every way an ID token or a return can be wrong turned away; the company account beside the
|
|
12468
|
+
local ones; the settings, the secret kept out of sight; the CLI, the page and the help."""
|
|
12469
|
+
import http.client
|
|
12470
|
+
import ssl as _ssl
|
|
12471
|
+
import subprocess as _sp
|
|
12472
|
+
import urllib.parse as _up
|
|
12473
|
+
import zipfile as _zf
|
|
12474
|
+
from . import __version__ as _ver
|
|
12475
|
+
from . import dossier as DZ
|
|
12476
|
+
from . import helplib as H
|
|
12477
|
+
from . import oidc as OI
|
|
12478
|
+
from . import tls as TLS
|
|
12479
|
+
from .oidc_mock import MockIdP
|
|
12480
|
+
from .service import Service, Users
|
|
12481
|
+
from .workspace import Workspace
|
|
12482
|
+
pkg = Path(__file__).parent
|
|
12483
|
+
idp = MockIdP()
|
|
12484
|
+
wsp = str(Path(tmp, "ws_cy")); ws = Workspace.create(wsp, "Company Site", actor="tester")
|
|
12485
|
+
users = Users(str(Path(wsp, "users.json")))
|
|
12486
|
+
users.add("adm", "admin-pass-123", "admin"); users.add("v", "viewer-pass-1", "viewer"); users.add("olga.local", "local-pass-12", "operator")
|
|
12487
|
+
TLS.make_self_signed(ws, "tester", ["localhost", "127.0.0.1"], days=60)
|
|
12488
|
+
svc = Service(wsp, host="127.0.0.1", port=0, scheduler=False)
|
|
12489
|
+
svc.app.oidc_ssl_context = idp.client_ctx
|
|
12490
|
+
svc.start()
|
|
12491
|
+
base = svc.url.rstrip("/")
|
|
12492
|
+
trust = _ssl.create_default_context(cafile=str(Path(wsp, TLS.SELF_CERT)))
|
|
12493
|
+
|
|
12494
|
+
def get(url, cookie=None, ctx=trust):
|
|
12495
|
+
u = _up.urlsplit(url)
|
|
12496
|
+
c = http.client.HTTPSConnection(u.hostname, u.port, context=ctx, timeout=30)
|
|
12497
|
+
c.request("GET", u.path + ("?" + u.query if u.query else ""), headers=({"Cookie": cookie} if cookie else {}))
|
|
12498
|
+
r = c.getresponse()
|
|
12499
|
+
b = r.read()
|
|
12500
|
+
c.close()
|
|
12501
|
+
return r.status, r.getheader("Location"), r.headers.get_all("Set-Cookie") or [], b
|
|
12502
|
+
|
|
12503
|
+
def api(method, path, body=None, cookie=None):
|
|
12504
|
+
c = http.client.HTTPSConnection("127.0.0.1", svc.port, context=trust, timeout=30)
|
|
12505
|
+
h = {"Content-Type": "application/json", "X-GEA-Action": "1"} if method == "POST" else {}
|
|
12506
|
+
if cookie:
|
|
12507
|
+
h["Cookie"] = cookie
|
|
12508
|
+
c.request(method, path, body=(json.dumps(body) if body is not None else None), headers=h)
|
|
12509
|
+
r = c.getresponse()
|
|
12510
|
+
b = r.read()
|
|
12511
|
+
c.close()
|
|
12512
|
+
return r.status, json.loads(b or b"{}"), r.headers.get_all("Set-Cookie") or []
|
|
12513
|
+
|
|
12514
|
+
def local(name, pw):
|
|
12515
|
+
return [x for x in api("POST", "/api/login", {"name": name, "password": pw})[2] if x.startswith("gea_session")][0].split(";")[0]
|
|
12516
|
+
|
|
12517
|
+
def round_trip(user=None, fault=None, keep_cookie=True):
|
|
12518
|
+
"""The browser's path: start -> the provider -> back. (status, the page, the session cookie or None, the callback URL, the flow cookie)"""
|
|
12519
|
+
idp.fault = fault
|
|
12520
|
+
if user:
|
|
12521
|
+
idp.user = user
|
|
12522
|
+
s1, loc1, ck1, _ = get(base + "/api/oidc/start")
|
|
12523
|
+
flow = ck1[0].split(";")[0] if ck1 else None
|
|
12524
|
+
s2, loc2, _, _ = get(loc1, ctx=idp.client_ctx)
|
|
12525
|
+
s3, _, ck3, b3 = get(loc2, cookie=(flow if keep_cookie else None))
|
|
12526
|
+
sess = next((x.split(";")[0] for x in ck3 if x.startswith("gea_session=") and not x.startswith("gea_session=;")), None)
|
|
12527
|
+
return s3, b3.decode("utf-8", "replace"), sess, loc2, flow, (s1, s2, ck1)
|
|
12528
|
+
olga = {"sub": "sub-olga", "preferred_username": "olga@example.com", "roles": ["GEA.Operator"]}
|
|
12529
|
+
try:
|
|
12530
|
+
adm = local("adm", "admin-pass-123"); vw = local("v", "viewer-pass-1")
|
|
12531
|
+
# -- CY1 the settings --------------------------------------------------------------------------------------------
|
|
12532
|
+
redir = base + "/api/oidc/callback"
|
|
12533
|
+
good = {"issuer": idp.issuer, "client_id": idp.client_id, "redirect_uri": redir,
|
|
12534
|
+
"role_map": {"GEA.Viewer": "viewer", "GEA.Operator": "operator", "GEA.Approver": "approver", "GEA.Admin": "admin"}}
|
|
12535
|
+
bad = [{**good, "redirect_uri": "http://gea-pad3.example.local:8765/api/oidc/callback"}, {**good, "redirect_uri": base + "/somewhere"},
|
|
12536
|
+
{**good, "issuer": "https://login.microsoftonline.com/common/v2.0"}, {**good, "issuer": "http://idp.example"},
|
|
12537
|
+
{**good, "role_map": {}}, {**good, "role_map": {"x": "superuser"}}, {**good, "client_id": ""}]
|
|
12538
|
+
bad_codes = [api("POST", "/api/oidc", {"action": "set", **b, "client_secret": "s"}, adm)[0] for b in bad]
|
|
12539
|
+
s_vset = api("POST", "/api/oidc", {"action": "set", **good, "client_secret": idp.client_secret}, vw)[0]
|
|
12540
|
+
s_set, st_set, _ = api("POST", "/api/oidc", {"action": "set", **good, "client_secret": idp.client_secret}, adm)
|
|
12541
|
+
s_get, st_get, _ = api("GET", "/api/oidc", cookie=adm)
|
|
12542
|
+
s_vget = api("GET", "/api/oidc", cookie=vw)[0]
|
|
12543
|
+
s_test, tst, _ = api("POST", "/api/oidc", {"action": "test"}, adm)
|
|
12544
|
+
sess_pub = api("GET", "/api/session")[1]
|
|
12545
|
+
secret_file = Path(wsp, OI.SECRET_FILE)
|
|
12546
|
+
c1 = {
|
|
12547
|
+
"refused": all(c == 400 for c in bad_codes) and s_vset == 403 and s_vget == 403,
|
|
12548
|
+
"kept": s_set == 200 and st_set["configured"] and st_set["secret"] == "kept" and s_get == 200 and st_get["issuer"] == idp.issuer
|
|
12549
|
+
and idp.client_secret not in json.dumps(st_get) and secret_file.read_text(encoding="utf-8") == idp.client_secret
|
|
12550
|
+
and (os.name == "nt" or (secret_file.stat().st_mode & 0o077) == 0),
|
|
12551
|
+
"test": s_test == 200 and tst["ok"] and tst["signing_keys"] == 1 and tst["token_endpoint"].endswith("/token"),
|
|
12552
|
+
"public": sess_pub.get("company_sign_in") == {"label": "Sign in with your company account"} and sess_pub.get("user") is None,
|
|
12553
|
+
}
|
|
12554
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
12555
|
+
ok(not failed1,
|
|
12556
|
+
"CY1 company sign-in is set by an administrator only: an http:// or wrong redirect URI, a multi-tenant issuer, an issuer not on https, no role map "
|
|
12557
|
+
"and no default, a role that does not exist, no client id - each refused; the secret is kept in keys\\ readable by its owner only and never sent "
|
|
12558
|
+
"back; Test reads the provider's discovery and keys; the sign-in page learns only that the button exists" + (f" - failed: {failed1}" if failed1 else ""))
|
|
12559
|
+
# -- CY2 the round trip --------------------------------------------------------------------------------------------
|
|
12560
|
+
s3, page, sess, cb_url, flow, (s1, s2, ck1) = round_trip(olga)
|
|
12561
|
+
me = api("GET", "/api/session", cookie=sess)[1]["user"] if sess else {}
|
|
12562
|
+
call = idp.token_calls[-1] if idp.token_calls else {}
|
|
12563
|
+
r_replay = get(cb_url, cookie=flow)
|
|
12564
|
+
s_replay, replay_body = r_replay[0], r_replay[3].decode()
|
|
12565
|
+
idp.user = dict(olga, roles=["GEA.Operator", "GEA.Admin"])
|
|
12566
|
+
s_up, _, sess_up, _, _, _ = round_trip()
|
|
12567
|
+
me_up = api("GET", "/api/session", cookie=sess_up)[1]["user"] if sess_up else {}
|
|
12568
|
+
listed = {u["name"]: u for u in api("GET", "/api/users", cookie=adm)[1]["users"]}
|
|
12569
|
+
live = [x for x in api("GET", "/api/sessions", cookie=adm)[1]["sessions"] if x["name"] == "olga@example.com"]
|
|
12570
|
+
c2 = {
|
|
12571
|
+
"start": s1 == 302 and "SameSite=Lax" in ck1[0] and "Secure" in ck1[0] and "Path=/api/oidc" in ck1[0] and s2 == 302,
|
|
12572
|
+
"back": s3 == 200 and 'http-equiv="refresh"' in page and sess is not None and me.get("name") == "olga@example.com" and me.get("role") == "operator"
|
|
12573
|
+
and me.get("via") == "company",
|
|
12574
|
+
"pkce": call.get("code_verifier") and call.get("client_secret") == idp.client_secret and call.get("redirect_uri") == redir,
|
|
12575
|
+
"once": s_replay == 401 and "unknown or has expired" in replay_body,
|
|
12576
|
+
"role_follows": me_up.get("role") == "admin" and listed["olga@example.com"]["kind"] == "company" and listed["olga@example.com"]["role"] == "admin"
|
|
12577
|
+
and live and all(x["via"] == "company" for x in live),
|
|
12578
|
+
}
|
|
12579
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
12580
|
+
ok(not failed2,
|
|
12581
|
+
"CY2 the round trip: the start sends the browser to the provider with a flow cookie (SameSite=Lax, Secure, this path only); back with a code, the "
|
|
12582
|
+
"service exchanges it with the client secret and the PKCE verifier, checks the ID token, and the person is signed in as a company account with the "
|
|
12583
|
+
"role their claims map to - the highest, taken again at every sign-in - by a page that moves on by itself (the session cookie stays Strict); "
|
|
12584
|
+
"a return is accepted once" + (f" - failed: {failed2}" if failed2 else ""))
|
|
12585
|
+
# -- CY3 every way it can be wrong ----------------------------------------------------------------------------------
|
|
12586
|
+
outcomes = {}
|
|
12587
|
+
for f in ("aud", "expired", "sig", "alg_none", "hs256", "nonce", "iss"):
|
|
12588
|
+
st_, pg_, se_, _, _, _ = round_trip(olga, fault=f)
|
|
12589
|
+
outcomes[f] = (st_, se_ is None, pg_)
|
|
12590
|
+
st_rot, _, se_rot, _, _, _ = round_trip(olga, fault="rotate")
|
|
12591
|
+
st_nb, pg_nb, se_nb, _, _, _ = round_trip(olga, keep_cookie=False)
|
|
12592
|
+
st_nr, pg_nr, se_nr, _, _, _ = round_trip({"sub": "sub-x", "preferred_username": "x@example.com", "roles": ["Other"]})
|
|
12593
|
+
st_lc, pg_lc, se_lc, _, _, _ = round_trip({"sub": "sub-l", "preferred_username": "olga.local", "roles": ["GEA.Admin"]})
|
|
12594
|
+
st_os, pg_os, se_os, _, _, _ = round_trip({"sub": "someone-else", "preferred_username": "olga@example.com", "roles": ["GEA.Admin"]})
|
|
12595
|
+
pg_ov = ""
|
|
12596
|
+
try:
|
|
12597
|
+
OI.role_of({"_claim_names": {"groups": "src1"}}, {**OI.DEFAULTS, "role_claim": "groups", "role_map": {"g": "viewer"}})
|
|
12598
|
+
except OI.OidcError as e:
|
|
12599
|
+
pg_ov = str(e)
|
|
12600
|
+
s_dis = api("POST", "/api/users/disable", {"name": "olga@example.com", "disabled": True}, adm)[0]
|
|
12601
|
+
st_dis, pg_dis, se_dis, _, _, _ = round_trip(olga)
|
|
12602
|
+
api("POST", "/api/users/disable", {"name": "olga@example.com", "disabled": False}, adm)
|
|
12603
|
+
s_pw = api("POST", "/api/users/password", {"name": "olga@example.com", "password": "whatever-123"}, adm)[0]
|
|
12604
|
+
s_role = api("POST", "/api/users/role", {"name": "olga@example.com", "role": "viewer"}, adm)[0]
|
|
12605
|
+
s_pwlogin = api("POST", "/api/login", {"name": "olga@example.com", "password": ""})[0]
|
|
12606
|
+
s_local = api("POST", "/api/login", {"name": "olga.local", "password": "local-pass-12"})[0]
|
|
12607
|
+
audit = Path(ws.audit_path).read_text(encoding="utf-8")
|
|
12608
|
+
why = {"aud": "audience", "expired": "expired", "sig": "signature", "alg_none": "only RS256", "hs256": "only RS256", "nonce": "nonce", "iss": "issued by"}
|
|
12609
|
+
c3 = {
|
|
12610
|
+
"tokens": all(outcomes[f][0] == 401 and outcomes[f][1] and why[f] in outcomes[f][2] for f in why),
|
|
12611
|
+
"rotation": st_rot == 200 and se_rot is not None,
|
|
12612
|
+
"binding": st_nb == 401 and se_nb is None and "another browser" in pg_nb,
|
|
12613
|
+
"no_role": st_nr == 401 and se_nr is None and "no role in GEA" in pg_nr and "Other" in pg_nr,
|
|
12614
|
+
"no_takeover": st_lc == 401 and se_lc is None and "local account" in pg_lc and st_os == 401 and se_os is None and "another company identity" in pg_os,
|
|
12615
|
+
"overage": "more groups than a token carries" in pg_ov,
|
|
12616
|
+
"disabled": s_dis == 200 and st_dis == 401 and se_dis is None and "disabled" in pg_dis,
|
|
12617
|
+
"no_password": s_pw == 400 and s_role == 400 and s_pwlogin in (400, 401) and s_local == 200,
|
|
12618
|
+
"audited": audit.count('"via": "company"') >= 3 and '"login.failed"' in audit and "audience" in audit,
|
|
12619
|
+
}
|
|
12620
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
12621
|
+
ok(not failed3,
|
|
12622
|
+
"CY3 turned away, each saying why, with no session: an ID token for another application, expired, with a bad signature, signed 'none' or HS256, "
|
|
12623
|
+
"with another sign-in's nonce, from another issuer; a return to a browser that did not start the sign-in; a person with no mapped role; a name "
|
|
12624
|
+
"a local account has, or a company account bound to another subject; a person in more groups than a token carries; a company account disabled "
|
|
12625
|
+
"in GEA. A rotated signing key is fetched and accepted. A company account has no password here and its role is not set by hand; local accounts "
|
|
12626
|
+
"sign in as before; every sign-in and failure is audited" + (f" - failed: {failed3}" if failed3 else ""))
|
|
12627
|
+
finally:
|
|
12628
|
+
svc.stop()
|
|
12629
|
+
# -- CY4 the CLI, the dossier, the page, the help -------------------------------------------------------------------
|
|
12630
|
+
env = {**os.environ, "GEA_OIDC_TEST_SECRET": "cli-secret-456", "SSL_CERT_FILE": idp.cafile}
|
|
12631
|
+
w2 = str(Path(tmp, "ws_cy2")); Workspace.create(w2, "CLI Site", actor="tester")
|
|
12632
|
+
cli_set = _sp.run([sys.executable, "-m", "gea", "oidc", "--workspace", w2, "--issuer", idp.issuer, "--client-id", idp.client_id,
|
|
12633
|
+
"--redirect-uri", "https://gea-pad3.example.local:8765/api/oidc/callback", "--secret-from-env", "GEA_OIDC_TEST_SECRET",
|
|
12634
|
+
"--map", "GEA.Admin=admin", "--map", "GEA.Viewer=viewer", "--default-role", "none", "--test"],
|
|
12635
|
+
capture_output=True, text=True, cwd=str(pkg.parent), env=env)
|
|
12636
|
+
cli_st = _sp.run([sys.executable, "-m", "gea", "oidc", "--workspace", w2, "--json"], capture_output=True, text=True, cwd=str(pkg.parent), env=env)
|
|
12637
|
+
cli_bad = _sp.run([sys.executable, "-m", "gea", "oidc", "--workspace", w2, "--map", "nonsense"], capture_output=True, text=True, cwd=str(pkg.parent), env=env)
|
|
12638
|
+
cli_off = _sp.run([sys.executable, "-m", "gea", "oidc", "--workspace", w2, "--off"], capture_output=True, text=True, cwd=str(pkg.parent), env=env)
|
|
12639
|
+
idp.stop()
|
|
12640
|
+
__import__("gea.frac_hits", fromlist=["write_report"]).write_report(ws, _ver, "tester")
|
|
12641
|
+
dz = DZ.build(ws, "tester", _ver, None, include_sources=True)
|
|
12642
|
+
with _zf.ZipFile(Path(ws.reports_dir, dz["path"])) as z:
|
|
12643
|
+
leaked = [n for n in z.namelist() if idp.client_secret.encode() in z.read(n)]
|
|
12644
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
12645
|
+
st_cli = json.loads(cli_st.stdout) if cli_st.returncode == 0 else {}
|
|
12646
|
+
c4 = {
|
|
12647
|
+
"cli": cli_set.returncode == 0 and "provider: GOOD" in cli_set.stdout and "GEA.Admin -> admin" in cli_set.stdout
|
|
12648
|
+
and st_cli.get("secret") == "kept" and Path(w2, OI.SECRET_FILE).read_text(encoding="utf-8") == "cli-secret-456"
|
|
12649
|
+
and "cli-secret-456" not in cli_st.stdout and cli_bad.returncode != 0 and "VALUE=ROLE" in (cli_bad.stderr + cli_bad.stdout)
|
|
12650
|
+
and cli_off.returncode == 0 and "local accounts only" in cli_off.stdout,
|
|
12651
|
+
"dossier": not leaked and idp.client_secret not in Path(ws.audit_path).read_text(encoding="utf-8"),
|
|
12652
|
+
"page": all(x in page_src for x in ("/api/oidc/start", "aCompany", "s.company_sign_in", "ocSave", "ocTest", "ocMap", "company account: role from its claims"))
|
|
12653
|
+
and "[hidden] { display: none !important; }" in page_src,
|
|
12654
|
+
"help": not H.check("company-sign-in") and "Entra" in (H.page("company-sign-in") or "") and "App roles" in (H.page("company-sign-in") or ""),
|
|
12655
|
+
}
|
|
12656
|
+
failed4 = [k for k, v in c4.items() if not v]
|
|
12657
|
+
ok(not failed4,
|
|
12658
|
+
"CY4 `gea oidc` sets company sign-in from a terminal (the secret read from an environment variable once and kept, never printed), tests the "
|
|
12659
|
+
"provider, refuses a malformed map and removes it; no dossier carries the client secret; the sign-in page's button and the Company sign-in card; "
|
|
12660
|
+
"the help page with the Entra ID steps" + (f" - failed: {failed4}" if failed4 else ""))
|
|
12661
|
+
|
|
12662
|
+
|
|
12464
12663
|
def PR_read_ok(path) -> bool:
|
|
12465
12664
|
from . import pdf_report as PR
|
|
12466
12665
|
try:
|
|
@@ -12574,6 +12773,7 @@ def main() -> int:
|
|
|
12574
12773
|
section_cv_torque_drag(tmp)
|
|
12575
12774
|
section_cw_decline(tmp)
|
|
12576
12775
|
section_cx_https(tmp)
|
|
12776
|
+
section_cy_company_sign_in(tmp)
|
|
12577
12777
|
if _FAILS:
|
|
12578
12778
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
12579
12779
|
for f in _FAILS:
|
|
@@ -257,7 +257,7 @@ def check_workspace(path: str, host: str = '127.0.0.1', port: int = 8765) -> Lis
|
|
|
257
257
|
from . import tls as TLS
|
|
258
258
|
ts = TLS.status(ws)
|
|
259
259
|
lvl = {'OFF': 'info', 'OK': 'ok', 'EXPIRING': 'warn', 'EXPIRED': 'warn', 'NOT_YET_VALID': 'warn', 'BROKEN': 'block'}[ts['state']]
|
|
260
|
-
fix = {'OFF': f'for the site network: gea tls --workspace "{path}" --site-cert
|
|
260
|
+
fix = {'OFF': f'for the site network: gea tls --workspace "{path}" --site-cert C:\\certs\\gea-pad3.crt --site-key C:\\certs\\gea-pad3.key, or --self-signed',
|
|
261
261
|
'EXPIRING': f'gea tls --workspace "{path}" shows the certificate; renew it (or --self-signed for a new one) and restart',
|
|
262
262
|
'EXPIRED': f'gea tls --workspace "{path}" --self-signed makes a new one; a site certificate is renewed by the site',
|
|
263
263
|
'NOT_YET_VALID': "set this machine's clock",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Company sign-in
|
|
2
|
+
|
|
3
|
+
> People sign in to the control panel with the account the company already manages - Microsoft Entra ID, Okta, Google Workspace, Ping, Keycloak or AD FS, by OpenID Connect - so the company's multi-factor, conditional access and leavers apply; the role in GEA comes from the app roles or groups the company gives them. Local accounts stay.
|
|
4
|
+
|
|
5
|
+
**The command**: Administration -> **Company sign-in** (the administrator role): the issuer, the client id and secret, the redirect URI, the role claim and the role map, a default role, the claim that names the person, the button's words; **Save**, **Test the provider**, **Remove company sign-in**. The sign-in page then shows the button above the local sign-in. From a terminal: `gea oidc --workspace C:\site` prints how it stands; `--issuer https://login.microsoftonline.com/3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64/v2.0 --client-id a41c7e92-0d3b-4f6a-9e85-2b6d1c4f7a30 --redirect-uri https://gea-pad3.example.local:8765/api/oidc/callback --secret-from-env GEA_OIDC_SECRET --map GEA.Viewer=viewer --map GEA.Operator=operator --map GEA.Approver=approver --map GEA.Admin=admin` sets it (the secret is read from that environment variable once and kept; `--secret-env NAME` has the service read it at every sign-in instead); `--test` reads the provider; `--pause`; `--off`.
|
|
6
|
+
|
|
7
|
+
**What it needs**: HTTPS on first (`gea help https`): a provider sends people back only to an https:// address (Entra ID allows http only for localhost). The machine running the service reaches the provider over HTTPS (Entra ID: `login.microsoftonline.com`), and its clock is right to within five minutes. For **Microsoft Entra ID**: in the Entra admin center, App registrations -> New registration - a name ("GEA control panel"), accounts in this organizational directory only, Redirect URI of the Web platform: the control panel's address followed by `/api/oidc/callback`, exactly. Its Overview gives the Application (client) ID and the Directory (tenant) ID: the issuer is `https://login.microsoftonline.com/`, the tenant ID, then `/v2.0` - for a tenant ID of 3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64, `https://login.microsoftonline.com/3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64/v2.0`. Certificates & secrets -> New client secret: its Value, shown once, is the client secret (it expires: set a reminder to renew it). App roles -> four roles, allowed member types Users/Groups, values `GEA.Viewer`, `GEA.Operator`, `GEA.Approver`, `GEA.Admin`. Enterprise applications -> the same application -> Users and groups: assign people or groups a role (assigning a group needs Entra ID P1 or P2), and under Properties set Assignment required, so only those assigned can sign in. Other providers: an OpenID Connect web application (a confidential client with a secret), the authorization code flow, the same redirect URI, ID tokens signed RS256, and a claim carrying the person's roles or groups.
|
|
8
|
+
|
|
9
|
+
**How it is read**: "Sign in with your company account" sends the browser to the provider with a fresh state, a nonce and a PKCE challenge (S256); a short-lived cookie binds that sign-in to the browser that started it. The provider sends the browser back with a code, which the service exchanges for an ID token with the client secret and the PKCE verifier, server to server. The ID token is checked with nothing outside the standard library: its RS256 signature against the provider's published keys (fetched again once when a token names a key not yet seen, which is how a provider rotates its keys), the issuer, the audience (this client), the expiry, the issue and not-before times (five minutes of clock difference allowed) and the nonce; any other algorithm, `none` included, is turned away, as is a state used twice or more than ten minutes old. The role is the highest any of the person's values in the role claim maps to; no mapped value and no default role, no sign-in, and the page says which values the person had. With the groups claim, Entra ID sends group object IDs (or the on-premises names for synced groups), and none at all for a person in more than 200 groups - app roles avoid both. The person is kept as a company account in `users.json`, named by the name claim and bound to the provider's subject: it has no password here, its role is set from the claims at every sign-in, an administrator can disable it in GEA, and a local account of the same name is never taken over. A failed company sign-in is audited with its reason; it does not count toward the local sign-in lock (the provider keeps its own), so a mistake in the role map cannot lock an office's local accounts out.
|
|
10
|
+
|
|
11
|
+
**What it writes**: the settings in `workspace.json` (`control.oidc`) with the audit line `oidc.settings` (and `oidc.off`); the client secret in `keys\oidc_client_secret.txt` (readable by its owner only; never shown, never in a report, an export or a dossier, sent only to the token endpoint) unless an environment variable is named; each sign-in as the audit line `login` with `via: company` and the claim values used, each failure as `login.failed` with the reason; the company accounts in `users.json`.
|
|
12
|
+
|
|
13
|
+
**The number to check**: **Test the provider** - GOOD, with at least one signing key and RS256 among the algorithms; then a sign-in from a private window by a person with each role, and the role shown at the top right against the role assigned in the company directory.
|
|
14
|
+
|
|
15
|
+
**What this page will not call a measurement**: who a person is - the company's directory says that, and GEA keeps what its signed token said at the moment they signed in. A person removed from the directory keeps a session already open here until it ends (12 hours) or an administrator revokes it under Live sessions.
|
|
16
|
+
|
|
17
|
+
**Sources**: OpenID Connect Core 1.0 (sections 3.1 and 3.1.3.7: the authorization code flow and the ID token's checks); RFC 6749 (OAuth 2.0) and RFC 7636 (PKCE); RFC 7515, 7517 and 7518 (JWS, JWK, the RS256 algorithm); RFC 8414 and OpenID Connect Discovery 1.0 (the discovery document); Microsoft, "Redirect URI (reply URL) outline and restrictions" (https required except localhost; at most 256 characters); Microsoft, "Configure group claims for applications by using Microsoft Entra ID" (at most 200 groups in a JWT, object IDs by default).
|
|
18
|
+
|
|
19
|
+
Related: `gea help https`, `gea help site`, `gea help upkeep`.
|
|
@@ -85,6 +85,7 @@ INDEX: List[tuple] = [
|
|
|
85
85
|
('audit-update', 'Audit / Update', 'the whole audit log with filters, the program against PyPI, every report against its source'),
|
|
86
86
|
('soak', 'The soak', 'the patches through link outages for hours: reconnection, resume, loss by protocol, the page answering - measured, not modelled'),
|
|
87
87
|
('https', 'HTTPS', "the control panel over HTTPS on the site network: the site's own certificate or a self-signed one made here, the names it carries, handing it out, renewal and the expiry alert"),
|
|
88
|
+
('company-sign-in', 'Company sign-in', "people sign in with the account the company manages (Entra ID, Okta, Google, Ping, Keycloak, AD FS by OpenID Connect), the role from its app roles or groups; local accounts stay"),
|
|
88
89
|
('upkeep', 'Upkeep', 'housekeeping, sessions, the TLS proxy, the load test'),
|
|
89
90
|
]
|
|
90
91
|
# the four lines every page must carry, in this order
|