gea-program 0.55.0__tar.gz → 0.57.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.57.0}/PKG-INFO +35 -2
- {gea_program-0.55.0 → gea_program-0.57.0}/README.md +34 -1
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/__init__.py +1 -1
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/__main__.py +74 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/acceptance_tests.py +362 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/cli.py +14 -1
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/doctor.py +1 -1
- gea_program-0.57.0/gea/help/IT_SECURITY.md +138 -0
- gea_program-0.57.0/gea/help/company-sign-in.md +19 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/helplib.py +9 -0
- gea_program-0.57.0/gea/oidc.py +379 -0
- gea_program-0.57.0/gea/oidc_mock.py +135 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/service.py +122 -4
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/web/app.html +21 -5
- {gea_program-0.55.0 → gea_program-0.57.0/gea_program.egg-info}/PKG-INFO +35 -2
- {gea_program-0.55.0 → gea_program-0.57.0}/gea_program.egg-info/SOURCES.txt +4 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/pyproject.toml +1 -1
- {gea_program-0.55.0 → gea_program-0.57.0}/LICENSE +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/alerts.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/backup.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/bench.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/blind_harness.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog_map.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/catalog_register.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/certificates.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/client_reports.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/collision.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/config_versioning.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/conformance.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/control_room.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/correlation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/dashboard.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/declarations.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/decline.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/deviation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/dlis.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/dnp3.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/dossier.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/dossier_verify.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/drill_string.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/earth_model.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/economics.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/ed25519.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/edr.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/elevation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/etp.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/example_register_map.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/fat_sat.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/files.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/follower.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/forward_model.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/frac_hits.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/friction_priors.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/gamma.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/geodesy.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/geomag.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/hartip.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/alarms.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/alerts.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/backup.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/catalog.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/charts.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/collision.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/conformance.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/control-room.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/data-in.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/declare.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/decline.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/dlis.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/doctor.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/dossier.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/drift.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/drillstring.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/economics.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/edr.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/etp.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/files.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/frac-hits.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/historian.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/https.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/instruments.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/interval.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/invoice-check.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/map.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/month-end.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/notifications.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/opseis.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/osdu.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/pad-summary.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/patches.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/penetrator.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/plan.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/pore-pressure.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/quality.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/report-history.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/rig-time.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/seismic.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/setup.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/simulation.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/site.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/sites.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/slip.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/soak.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/sra.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/start.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/structure.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/tools.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/torque-drag.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/transients.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/vibration.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/well-report.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/help/well3d.md +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/housekeeping.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/interval.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/invoice_check.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/jobs.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/live_ports.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/loadtest.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/modbus.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/model_card.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/month_end.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/notify.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/op_seismicity.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/opcua_port.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/operator_app.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/osdu.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/pad_summary.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/patches.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/pdf_report.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/penetrator.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/permits.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/piwebapi.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/pore_pressure.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/ports.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/ppdm.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/project.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/quakeml.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reconciler.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/WMM2025.COF +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/eia_prices.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/iscwsa_rev4_example.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/iscwsa_rev5_example.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/mahoning_example.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/mud_motor_specs.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/owsg_tool_models.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/penetrator_ratings.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/seismicity_protocols.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_3d.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_surfaces.json.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/report_history.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/rig_state.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/sample_record.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/sbom.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seedlink.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/segy.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_array.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_film.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_response.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_track.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/seismic_watch.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/series_cache.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/service_life.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/shapefile.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/shell.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/shut_in.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/simulation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/site_map.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/sla_report.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/slip.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/soak.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/sra.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/store_forward.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/strata_join.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/structure.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/supervisor.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/survey_view.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/telemetry.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/tls.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/tool_catalog.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/tool_library.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/torque_drag.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/transient.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/vibration.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/well3d.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/well_assembler.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/well_report.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/wellplan.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/wellpos.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/wits0.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/witsml.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/workspace.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea/xlsx_read.py +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.55.0 → gea_program-0.57.0}/setup.cfg +0 -0
- {gea_program-0.55.0 → gea_program-0.57.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.57.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,38 @@ 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
|
+
|
|
1294
|
+
### For an IT department
|
|
1295
|
+
|
|
1296
|
+
`docs/IT_SECURITY.md` (`gea security` prints it from any installation)
|
|
1297
|
+
answers what a reviewer asks before installing: where the data is and every
|
|
1298
|
+
connection the program can make, the one port, HTTPS, company sign-in and
|
|
1299
|
+
roles, the audit log, the bill of materials (`gea sbom`), and how a kit is
|
|
1300
|
+
checked - the build's attestation
|
|
1301
|
+
(`gea-program-0.57.0-win64.zip` checked with
|
|
1302
|
+
`gh attestation verify gea-program-0.57.0-win64.zip -R Daniel8Murphy0007/GEA-Program`)
|
|
1303
|
+
and the file catalog beside the kit's folder, Authenticode-signed once the
|
|
1304
|
+
publisher's certificate is in place. `SECURITY.md` says how to report a
|
|
1305
|
+
vulnerability.
|
|
1306
|
+
|
|
1275
1307
|
### Before a site goes live
|
|
1276
1308
|
|
|
1277
1309
|
`deploy/README.md` is the checklist: HTTPS from the service itself (above),
|
|
@@ -1403,4 +1435,5 @@ https://pypi.org/project/gea-program/ after the first successful run.
|
|
|
1403
1435
|
## Licence
|
|
1404
1436
|
|
|
1405
1437
|
Mozilla Public License 2.0 (MPL-2.0); see `LICENSE`. Every source file carries the MPL-2.0
|
|
1406
|
-
header (Exhibit A). Copyright (c) 2026 Daniel T. Murphy.
|
|
1438
|
+
header (Exhibit A). Copyright (c) 2026 Daniel T. Murphy. The signed kit, its updates and
|
|
1439
|
+
support are a subscription from ENRGYONE (`docs/commercial/COMMERCIAL.md`).
|
|
@@ -1233,6 +1233,38 @@ 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
|
+
|
|
1255
|
+
### For an IT department
|
|
1256
|
+
|
|
1257
|
+
`docs/IT_SECURITY.md` (`gea security` prints it from any installation)
|
|
1258
|
+
answers what a reviewer asks before installing: where the data is and every
|
|
1259
|
+
connection the program can make, the one port, HTTPS, company sign-in and
|
|
1260
|
+
roles, the audit log, the bill of materials (`gea sbom`), and how a kit is
|
|
1261
|
+
checked - the build's attestation
|
|
1262
|
+
(`gea-program-0.57.0-win64.zip` checked with
|
|
1263
|
+
`gh attestation verify gea-program-0.57.0-win64.zip -R Daniel8Murphy0007/GEA-Program`)
|
|
1264
|
+
and the file catalog beside the kit's folder, Authenticode-signed once the
|
|
1265
|
+
publisher's certificate is in place. `SECURITY.md` says how to report a
|
|
1266
|
+
vulnerability.
|
|
1267
|
+
|
|
1236
1268
|
### Before a site goes live
|
|
1237
1269
|
|
|
1238
1270
|
`deploy/README.md` is the checklist: HTTPS from the service itself (above),
|
|
@@ -1364,4 +1396,5 @@ https://pypi.org/project/gea-program/ after the first successful run.
|
|
|
1364
1396
|
## Licence
|
|
1365
1397
|
|
|
1366
1398
|
Mozilla Public License 2.0 (MPL-2.0); see `LICENSE`. Every source file carries the MPL-2.0
|
|
1367
|
-
header (Exhibit A). Copyright (c) 2026 Daniel T. Murphy.
|
|
1399
|
+
header (Exhibit A). Copyright (c) 2026 Daniel T. Murphy. The signed kit, its updates and
|
|
1400
|
+
support are a subscription from ENRGYONE (`docs/commercial/COMMERCIAL.md`).
|
|
@@ -67,7 +67,16 @@ def _build_config(a):
|
|
|
67
67
|
return SimulatorConfig(**kw)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
# the front door's own commands (gea.cli), reached the same way through `python -m gea` - the kit's gea.cmd and
|
|
71
|
+
# gea wrapper run `python -m gea`, and before v0.57 `gea guide`, `gea help` and the rest answered "invalid choice"
|
|
72
|
+
FRONT_ONLY = ('quickstart', 'guide', 'security', 'docs', 'gui', 'help')
|
|
73
|
+
|
|
74
|
+
|
|
70
75
|
def main(argv=None) -> int:
|
|
76
|
+
_argv = list(sys.argv[1:] if argv is None else argv)
|
|
77
|
+
if _argv and _argv[0] in FRONT_ONLY:
|
|
78
|
+
from .cli import main as _front
|
|
79
|
+
return _front(_argv)
|
|
71
80
|
ap = argparse.ArgumentParser(prog="gea",
|
|
72
81
|
description="GEA Downhole Simulator - headless CLI")
|
|
73
82
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
@@ -776,6 +785,24 @@ def main(argv=None) -> int:
|
|
|
776
785
|
p_tl.add_argument("--actor", type=str, default="cli")
|
|
777
786
|
p_tl.add_argument("--json", action="store_true")
|
|
778
787
|
|
|
788
|
+
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")
|
|
789
|
+
p_oc.add_argument("--workspace", type=str, required=True)
|
|
790
|
+
p_oc.add_argument("--issuer", type=str, default=None, help="Entra ID: https://login.microsoftonline.com/3f8b2c1d-6a4e-4d9b-8c2f-1e7a5b9d0c64/v2.0")
|
|
791
|
+
p_oc.add_argument("--client-id", type=str, default=None)
|
|
792
|
+
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")
|
|
793
|
+
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")
|
|
794
|
+
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)")
|
|
795
|
+
p_oc.add_argument("--role-claim", type=str, default=None, help="roles (Entra ID app roles, the default) or groups, or any claim")
|
|
796
|
+
p_oc.add_argument("--map", action="append", default=[], help="VALUE=ROLE, e.g. GEA.Admin=admin (repeat)")
|
|
797
|
+
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")
|
|
798
|
+
p_oc.add_argument("--name-claim", type=str, default=None, help="the claim that names the person (preferred_username by default)")
|
|
799
|
+
p_oc.add_argument("--label", type=str, default=None, help="the sign-in button's words")
|
|
800
|
+
p_oc.add_argument("--pause", action="store_true", help="keep the settings, stop company sign-in")
|
|
801
|
+
p_oc.add_argument("--test", action="store_true", help="read the provider's discovery document and keys now")
|
|
802
|
+
p_oc.add_argument("--off", action="store_true", help="remove company sign-in (local accounts are unchanged)")
|
|
803
|
+
p_oc.add_argument("--actor", type=str, default="cli")
|
|
804
|
+
p_oc.add_argument("--json", action="store_true")
|
|
805
|
+
|
|
779
806
|
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
807
|
p_hk.add_argument("--workspace", type=str, required=True)
|
|
781
808
|
p_hk.add_argument("--apply", action="store_true")
|
|
@@ -2616,6 +2643,53 @@ def main(argv=None) -> int:
|
|
|
2616
2643
|
raise SystemExit(f"pore-pressure: {e.args[0] if e.args else e}")
|
|
2617
2644
|
print(_json.dumps(r, default=str) if a.json else _PPF.text(r))
|
|
2618
2645
|
return 0
|
|
2646
|
+
elif a.cmd == "oidc":
|
|
2647
|
+
import json as _json
|
|
2648
|
+
from . import oidc as _OI
|
|
2649
|
+
from .workspace import Workspace, WorkspaceError
|
|
2650
|
+
try:
|
|
2651
|
+
ws = Workspace(a.workspace)
|
|
2652
|
+
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,
|
|
2653
|
+
a.default_role, a.name_claim, a.label)) or a.map or a.pause
|
|
2654
|
+
if a.off:
|
|
2655
|
+
r = _OI.turn_off(ws, a.actor)
|
|
2656
|
+
elif changing:
|
|
2657
|
+
cur = _OI.settings(ws) or {}
|
|
2658
|
+
s = {k: cur.get(k, v) for k, v in _OI.DEFAULTS.items()}
|
|
2659
|
+
for k, v in (("issuer", a.issuer), ("client_id", a.client_id), ("redirect_uri", a.redirect_uri), ("role_claim", a.role_claim),
|
|
2660
|
+
("name_claim", a.name_claim), ("label", a.label), ("client_secret_env", a.secret_env)):
|
|
2661
|
+
if v is not None:
|
|
2662
|
+
s[k] = v
|
|
2663
|
+
if a.default_role is not None:
|
|
2664
|
+
s["default_role"] = None if a.default_role.lower() in ("", "none") else a.default_role
|
|
2665
|
+
if a.map:
|
|
2666
|
+
m = {}
|
|
2667
|
+
for x in a.map:
|
|
2668
|
+
v_, _, r_ = x.rpartition("=")
|
|
2669
|
+
if not v_:
|
|
2670
|
+
raise ValueError(f"--map takes VALUE=ROLE, not {x!r}")
|
|
2671
|
+
m[v_.strip()] = r_.strip()
|
|
2672
|
+
s["role_map"] = m
|
|
2673
|
+
if a.secret_from_env:
|
|
2674
|
+
s["client_secret_env"] = ""
|
|
2675
|
+
s["enabled"] = not a.pause
|
|
2676
|
+
sec = None
|
|
2677
|
+
if a.secret_from_env:
|
|
2678
|
+
sec = os.environ.get(a.secret_from_env, "")
|
|
2679
|
+
if not sec:
|
|
2680
|
+
raise ValueError(f"the environment variable {a.secret_from_env} is empty")
|
|
2681
|
+
r = _OI.configure(ws, a.actor, s, sec)
|
|
2682
|
+
else:
|
|
2683
|
+
r = _OI.status(ws)
|
|
2684
|
+
if a.test:
|
|
2685
|
+
r = {**r, "test": _OI.test(ws)}
|
|
2686
|
+
except (KeyError, ValueError, OSError, WorkspaceError) as e:
|
|
2687
|
+
raise SystemExit(f"oidc: {e.args[0] if e.args else e}")
|
|
2688
|
+
if a.json:
|
|
2689
|
+
print(_json.dumps(r, default=str))
|
|
2690
|
+
else:
|
|
2691
|
+
print(_OI.text(r))
|
|
2692
|
+
return 0
|
|
2619
2693
|
elif a.cmd == "tls":
|
|
2620
2694
|
import json as _json
|
|
2621
2695
|
from . import tls as _TLS
|
|
@@ -12461,6 +12461,366 @@ 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
|
+
|
|
12663
|
+
def section_cz_it_review(tmp: str) -> None:
|
|
12664
|
+
"""Section CZ - what an IT department asks: the security overview ships with the program and agrees with the
|
|
12665
|
+
code it describes (the rounds, the sessions, the lock, the port, TLS, every host the program can reach); `gea
|
|
12666
|
+
security`, `gea guide` and `gea help` answer through `python -m gea` as the kit's launcher runs it; in a checkout,
|
|
12667
|
+
the kit tools zip the kit again with its catalog beside it and tell the site how to check it, and the build
|
|
12668
|
+
workflow attests both kits and signs the catalog only when a certificate is configured."""
|
|
12669
|
+
import importlib.util
|
|
12670
|
+
import json as _json
|
|
12671
|
+
import subprocess as _sp
|
|
12672
|
+
import urllib.parse as _up
|
|
12673
|
+
import zipfile as _zf
|
|
12674
|
+
from . import doctor as DR
|
|
12675
|
+
from . import elevation as EL
|
|
12676
|
+
from . import helplib as H
|
|
12677
|
+
from . import seismic_watch as SW
|
|
12678
|
+
from . import service as SVC
|
|
12679
|
+
from . import tls as TLS
|
|
12680
|
+
pkg = Path(__file__).parent
|
|
12681
|
+
checkout = (pkg.parent / "pyproject.toml").exists() and (pkg.parent / "tools" / "build_installer.py").exists()
|
|
12682
|
+
|
|
12683
|
+
# -- CZ1 the overview, and the code it describes -------------------------------------------------------------------
|
|
12684
|
+
text = H.security_text() or ""
|
|
12685
|
+
docs_copy = pkg.parent / "docs" / "IT_SECURITY.md"
|
|
12686
|
+
same = (not docs_copy.exists()) or docs_copy.read_text(encoding="utf-8") == text
|
|
12687
|
+
flat = " ".join(text.split())
|
|
12688
|
+
with open(Path(pkg, "reference", "eia_prices.provenance.json"), encoding="utf-8") as f:
|
|
12689
|
+
eia_hosts = {_up.urlsplit(s["refresh_url"]).hostname for s in _json.load(f)["series"].values() if s.get("refresh_url")}
|
|
12690
|
+
doctor_src = Path(DR.__file__).read_text(encoding="utf-8")
|
|
12691
|
+
hosts = {_up.urlsplit(EL.EPQS_URL).hostname, _up.urlsplit(SW.QUERY_URL).hostname, _up.urlsplit(SW.FEED_URL).hostname, *eia_hosts}
|
|
12692
|
+
hosts |= {"pypi.org"} if "https://pypi.org/pypi/gea-program/json" in doctor_src else {"(the update check moved)"}
|
|
12693
|
+
facts = {
|
|
12694
|
+
"rounds": f"{SVC.PBKDF2_ROUNDS:,}" in flat,
|
|
12695
|
+
"sessions": f"{SVC.SESSION_HOURS} hours" in flat and "256-bit" in flat,
|
|
12696
|
+
"lock": SVC.LOGIN_MAX_FAILURES == 5 and "five failures" in flat and f"{SVC.LOGIN_LOCK_S // 60} minutes" in flat,
|
|
12697
|
+
"port": "TCP 8765" in flat and "8765" in Path(DR.__file__).read_text(encoding="utf-8"),
|
|
12698
|
+
"tls": "TLS 1.2 minimum" in flat and f"last {TLS.EXPIRING_DAYS} days" in flat,
|
|
12699
|
+
"hosts": all(f"`{h}`" in flat for h in hosts),
|
|
12700
|
+
"no_telemetry": "No telemetry" in flat,
|
|
12701
|
+
}
|
|
12702
|
+
# Python 3.14's argparse formats every help string as the parser is built (help % params) and stops on a bare
|
|
12703
|
+
# "%": v0.54.0's first ship stopped at its gate that way while 3.13 passed. The same check, on any Python.
|
|
12704
|
+
import argparse as _ap
|
|
12705
|
+
import contextlib as _cl
|
|
12706
|
+
import io as _io
|
|
12707
|
+
from . import __main__ as _M
|
|
12708
|
+
bad_help = []
|
|
12709
|
+
_orig_add, _orig_sub = _ap.ArgumentParser.add_argument, _ap._SubParsersAction.add_parser
|
|
12710
|
+
_params = {k_: "x" for k_ in ("prog", "default", "dest", "type", "choices", "metavar", "required", "nargs", "const", "option_strings")}
|
|
12711
|
+
|
|
12712
|
+
def _fmt_ok(h):
|
|
12713
|
+
try:
|
|
12714
|
+
h % _params
|
|
12715
|
+
return True
|
|
12716
|
+
except (ValueError, TypeError, KeyError):
|
|
12717
|
+
return False
|
|
12718
|
+
|
|
12719
|
+
def _add(self, *a_, **k_):
|
|
12720
|
+
h_ = k_.get("help")
|
|
12721
|
+
if isinstance(h_, str) and h_ != _ap.SUPPRESS and not _fmt_ok(h_):
|
|
12722
|
+
bad_help.append(f"{self.prog} {'/'.join(a_)}")
|
|
12723
|
+
return _orig_add(self, *a_, **k_)
|
|
12724
|
+
|
|
12725
|
+
def _sub(self, name, **k_):
|
|
12726
|
+
h_ = k_.get("help")
|
|
12727
|
+
if isinstance(h_, str) and not _fmt_ok(h_):
|
|
12728
|
+
bad_help.append(f"gea {name}")
|
|
12729
|
+
return _orig_sub(self, name, **k_)
|
|
12730
|
+
_ap.ArgumentParser.add_argument, _ap._SubParsersAction.add_parser = _add, _sub
|
|
12731
|
+
try:
|
|
12732
|
+
with _cl.redirect_stdout(_io.StringIO()), _cl.redirect_stderr(_io.StringIO()):
|
|
12733
|
+
try:
|
|
12734
|
+
_M.main(["wells"])
|
|
12735
|
+
except SystemExit:
|
|
12736
|
+
pass
|
|
12737
|
+
finally:
|
|
12738
|
+
_ap.ArgumentParser.add_argument, _ap._SubParsersAction.add_parser = _orig_add, _orig_sub
|
|
12739
|
+
facts["help_strings"] = not bad_help
|
|
12740
|
+
r_sec = _sp.run([sys.executable, "-m", "gea", "security"], capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
12741
|
+
r_guide = _sp.run([sys.executable, "-m", "gea", "guide"], capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
12742
|
+
r_help = _sp.run([sys.executable, "-m", "gea", "help", "https"], capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
12743
|
+
bad = [k for k, v in facts.items() if not v]
|
|
12744
|
+
ok(text.startswith("# GEA-Program for an IT department") and same and not bad
|
|
12745
|
+
and r_sec.returncode == 0 and "GEA-Program for an IT department" in r_sec.stdout
|
|
12746
|
+
and r_guide.returncode == 0 and "HOW TO TEST GEA" in r_guide.stdout and r_help.returncode == 0 and "**The command**" in r_help.stdout,
|
|
12747
|
+
"CZ1 the security overview ships in the package (identical to docs/IT_SECURITY.md) and agrees with the code: the password hashing rounds, the "
|
|
12748
|
+
"session length and token size, the sign-in lock, the one port, the TLS floor and the expiry warning, every host the program can reach "
|
|
12749
|
+
"(ComCat, the elevation service, the price series, PyPI); `python -m gea security`, `guide` and `help` answer as the kit's own launcher "
|
|
12750
|
+
"runs them; every option's help text formats the way Python 3.14 checks it as the parser is built"
|
|
12751
|
+
+ (f" - disagrees: {bad}" if bad else "") + (f" - help texts: {bad_help}" if bad_help else ""))
|
|
12752
|
+
|
|
12753
|
+
# -- CZ2 the kit is zipped again with its catalog, and says how to check it ----------------------------------------------
|
|
12754
|
+
if checkout:
|
|
12755
|
+
spec = importlib.util.spec_from_file_location("cz_installer", str(pkg.parent / "tools" / "build_installer.py"))
|
|
12756
|
+
bi = importlib.util.module_from_spec(spec); spec.loader.exec_module(bi)
|
|
12757
|
+
kit = Path(tmp, "cz_kit", "gea-program-9.9.9-win64"); (kit / "python").mkdir(parents=True)
|
|
12758
|
+
(kit / "MANIFEST.json").write_text("{}", encoding="utf-8"); (kit / "python" / "python.exe").write_bytes(b"MZ stand-in")
|
|
12759
|
+
(kit / "SHA256SUMS.txt").write_text("x MANIFEST.json\n", encoding="utf-8")
|
|
12760
|
+
cat = Path(tmp, "cz_kit", "gea-program-9.9.9-win64.cat"); cat.write_bytes(b"0\x82 a catalog stand-in")
|
|
12761
|
+
zp = bi.rezip(kit, [cat])
|
|
12762
|
+
with _zf.ZipFile(zp) as z:
|
|
12763
|
+
names = sorted(z.namelist())
|
|
12764
|
+
cat_back = z.read("gea-program-9.9.9-win64.cat")
|
|
12765
|
+
try:
|
|
12766
|
+
bi.rezip(Path(tmp, "cz_kit"), [])
|
|
12767
|
+
not_kit = "accepted"
|
|
12768
|
+
except SystemExit as e:
|
|
12769
|
+
not_kit = str(e)
|
|
12770
|
+
readme = bi.README.format(version="9.9.9", target="Windows x64", pyline="p", install="i", start="s", verify="v", port="8765",
|
|
12771
|
+
service="sv", stop="st", uninstall="u", workspace="w", check=bi.WIN_CHECK.format(version="9.9.9"))
|
|
12772
|
+
build_src = (pkg.parent / "tools" / "build_installer.py").read_text(encoding="utf-8")
|
|
12773
|
+
c2 = {
|
|
12774
|
+
"rezip": names == ["gea-program-9.9.9-win64.cat", "gea-program-9.9.9-win64/MANIFEST.json", "gea-program-9.9.9-win64/SHA256SUMS.txt",
|
|
12775
|
+
"gea-program-9.9.9-win64/python/python.exe"] and cat_back == cat.read_bytes() and "not a kit folder" in not_kit,
|
|
12776
|
+
"readme": all(x in readme for x in ("gh attestation verify gea-program-9.9.9-win64.zip -R Daniel8Murphy0007/GEA-Program",
|
|
12777
|
+
"Test-FileCatalog -CatalogFilePath .\\gea-program-9.9.9-win64.cat -Path .\\gea-program-9.9.9-win64",
|
|
12778
|
+
"Get-AuthenticodeSignature", "IT_SECURITY.md", "gea help https"))
|
|
12779
|
+
and "368 checks" not in readme and "behind the site's TLS proxy" not in readme,
|
|
12780
|
+
"ships_overview": "docs' / 'IT_SECURITY.md'" in build_src and "kit / 'IT_SECURITY.md'" in build_src,
|
|
12781
|
+
}
|
|
12782
|
+
where = "the checkout's kit tools"
|
|
12783
|
+
else:
|
|
12784
|
+
c2 = {"installed": bool(H.security_text())}
|
|
12785
|
+
where = "an installed copy: the kit tools are checked in the checkout"
|
|
12786
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
12787
|
+
ok(not failed2,
|
|
12788
|
+
f"CZ2 ({where}) the kit is zipped again from its folder with its file catalog beside the folder, every file kept; a folder that is not a kit "
|
|
12789
|
+
"is refused; the kit's README tells the site how to check the build's attestation and every file against the catalog and its signature, "
|
|
12790
|
+
"and carries the IT overview" + (f" - failed: {failed2}" if failed2 else ""))
|
|
12791
|
+
|
|
12792
|
+
# -- CZ3 the build workflow: attested, signed only with a certificate, never signed in name only ----------------------------
|
|
12793
|
+
wf = pkg.parent / ".github" / "workflows" / "build-installer.yml"
|
|
12794
|
+
if wf.exists():
|
|
12795
|
+
y = wf.read_text(encoding="utf-8")
|
|
12796
|
+
win = y[y.index("windows-kit:"):y.index("linux-kit:")]
|
|
12797
|
+
lin = y[y.index("linux-kit:"):]
|
|
12798
|
+
order = [win.index(s) for s in ("New-FileCatalog", "azure/artifact-signing-action", "signtool", "Test-FileCatalog", "--rezip", "call install.cmd",
|
|
12799
|
+
"attest-build-provenance", "attach-kit.sh")]
|
|
12800
|
+
c3 = {
|
|
12801
|
+
"permissions": "id-token: write" in y and "attestations: write" in y and "contents: write" in y,
|
|
12802
|
+
"attested": "actions/attest-build-provenance" in win and "actions/attest-build-provenance" in lin
|
|
12803
|
+
and "subject-path: dist/gea-program-*-win64.zip" in win and "subject-path: dist/gea-program-*-linux-*.zip" in lin,
|
|
12804
|
+
"conditional": "if: env.ARTIFACT_SIGNING_ACCOUNT != ''" in win and "if: env.ARTIFACT_SIGNING_ACCOUNT == '' && env.SIGNING_PFX_BASE64 != ''" in win
|
|
12805
|
+
and "secrets.ARTIFACT_SIGNING_ACCOUNT" in win and "secrets.SIGNING_PFX_BASE64" in win,
|
|
12806
|
+
"checked": "-CatalogVersion 2.0" in win and "throw 'the catalog does not match the kit'" in win
|
|
12807
|
+
and "throw 'a certificate is configured but the catalog is not validly signed'" in win and "ships unsigned" in win
|
|
12808
|
+
and "Remove-Item $pfx" in win and "/tr http://timestamp" in win and "timestamp-rfc3161" in win,
|
|
12809
|
+
"order": order == sorted(order),
|
|
12810
|
+
"policy": (pkg.parent / "SECURITY.md").exists() and "Report a vulnerability" in " ".join((pkg.parent / "SECURITY.md").read_text(encoding="utf-8").split()),
|
|
12811
|
+
}
|
|
12812
|
+
where3 = "the checkout's workflow"
|
|
12813
|
+
else:
|
|
12814
|
+
c3 = {"installed": True}
|
|
12815
|
+
where3 = "an installed copy: the workflow is checked in the checkout"
|
|
12816
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
12817
|
+
ok(not failed3,
|
|
12818
|
+
f"CZ3 ({where3}) both kits are attested by the build that made them; the Windows kit's file catalog is made, signed by Azure Artifact "
|
|
12819
|
+
"Signing or a PFX only when its secrets are configured (timestamped; the PFX removed), checked against the kit - a mismatch, or a "
|
|
12820
|
+
"configured certificate that did not sign, stops the build - then zipped beside the kit before the kit is installed and its gate run; "
|
|
12821
|
+
"SECURITY.md says how to report a vulnerability" + (f" - failed: {failed3}" if failed3 else ""))
|
|
12822
|
+
|
|
12823
|
+
|
|
12464
12824
|
def PR_read_ok(path) -> bool:
|
|
12465
12825
|
from . import pdf_report as PR
|
|
12466
12826
|
try:
|
|
@@ -12574,6 +12934,8 @@ def main() -> int:
|
|
|
12574
12934
|
section_cv_torque_drag(tmp)
|
|
12575
12935
|
section_cw_decline(tmp)
|
|
12576
12936
|
section_cx_https(tmp)
|
|
12937
|
+
section_cy_company_sign_in(tmp)
|
|
12938
|
+
section_cz_it_review(tmp)
|
|
12577
12939
|
if _FAILS:
|
|
12578
12940
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
12579
12941
|
for f in _FAILS:
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
gea model-cards ... gea store-forward ... gea sla-report ... gea fat-sat ...
|
|
11
11
|
gea help [topic] the help library, by the job (gea help drift); same text as the dashboard's panels
|
|
12
12
|
gea guide the click-by-click tester guide
|
|
13
|
+
gea security what an IT department asks before installing, answered (docs/IT_SECURITY.md)
|
|
13
14
|
gea docs where the catalogue and the reports live on this machine
|
|
14
15
|
gea gui the desktop window (desktop extra)
|
|
15
16
|
gea accept the product gate
|
|
@@ -23,7 +24,7 @@ import os
|
|
|
23
24
|
import sys
|
|
24
25
|
from pathlib import Path
|
|
25
26
|
|
|
26
|
-
FRONT = {'quickstart', 'survey', 'guide', 'docs', 'gui', 'help', '-h', '--help'}
|
|
27
|
+
FRONT = {'quickstart', 'survey', 'guide', 'security', 'docs', 'gui', 'help', '-h', '--help'}
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def _pkg_root() -> Path:
|
|
@@ -62,6 +63,16 @@ def cmd_guide(_argv) -> int:
|
|
|
62
63
|
return 0
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
def cmd_security(_argv) -> int:
|
|
67
|
+
from .helplib import security_text
|
|
68
|
+
text = security_text() # ships inside the package (gea/help/IT_SECURITY.md)
|
|
69
|
+
if text is None:
|
|
70
|
+
print('the security overview is missing from this installation; reinstall: python -m pip install --upgrade --force-reinstall "gea-program[live]"')
|
|
71
|
+
return 2
|
|
72
|
+
print(text)
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
65
76
|
def cmd_help(argv) -> int:
|
|
66
77
|
from .helplib import page, index_text, topics
|
|
67
78
|
if not argv:
|
|
@@ -141,6 +152,8 @@ def main(argv=None) -> int:
|
|
|
141
152
|
return cmd_survey(rest)
|
|
142
153
|
if cmd == 'guide':
|
|
143
154
|
return cmd_guide(rest)
|
|
155
|
+
if cmd == 'security':
|
|
156
|
+
return cmd_security(rest)
|
|
144
157
|
if cmd == 'docs':
|
|
145
158
|
return cmd_docs(rest)
|
|
146
159
|
if cmd == 'gui':
|