• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Open-MSS / MSS / 10653123390

01 Sep 2024 10:12AM UTC coverage: 69.967% (-0.07%) from 70.037%
10653123390

Pull #2495

github

web-flow
Merge d3a10b8f0 into 0b95679f6
Pull Request #2495: remove the conda/mamba based updater.

24 of 41 new or added lines in 5 files covered. (58.54%)

92 existing lines in 6 files now uncovered.

13843 of 19785 relevant lines covered (69.97%)

0.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.36
/mslib/mscolab/mscolab.py
1
# -*- coding: utf-8 -*-
2
"""
3

4
    mslib.mscolab.server
5
    ~~~~~~~~~~~~~~~~~~~~
6

7
    Server for mscolab module
8

9
    This file is part of MSS.
10

11
    :copyright: Copyright 2019 Shivashis Padhi
12
    :copyright: Copyright 2019-2024 by the MSS team, see AUTHORS.
13
    :license: APACHE-2.0, see LICENSE for details.
14

15
    Licensed under the Apache License, Version 2.0 (the "License");
16
    you may not use this file except in compliance with the License.
17
    You may obtain a copy of the License at
18

19
       http://www.apache.org/licenses/LICENSE-2.0
20

21
    Unless required by applicable law or agreed to in writing, software
22
    distributed under the License is distributed on an "AS IS" BASIS,
23
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
    See the License for the specific language governing permissions and
25
    limitations under the License.
26
"""
27

28
import argparse
1✔
29
import logging
1✔
30
import platform
1✔
31
import os
1✔
32
import shutil
1✔
33
import sys
1✔
34
import secrets
1✔
35
import subprocess
1✔
36
import git
1✔
37
import flask_migrate
1✔
38
import pathlib
1✔
39

40
from mslib import __version__
1✔
41
from mslib.mscolab import migrations
1✔
42
from mslib.mscolab.conf import mscolab_settings
1✔
43
from mslib.mscolab.seed import seed_data, add_user, add_all_users_default_operation, \
1✔
44
    add_all_users_to_all_operations, delete_user
45
from mslib.mscolab.server import APP
1✔
46
from mslib.mscolab.utils import create_files
1✔
47
from mslib.utils import setup_logging
1✔
48

49

50
def handle_start(args):
1✔
51
    from mslib.mscolab.server import APP, sockio, cm, fm, start_server
×
52
    setup_logging(args)
×
53
    logging.info("MSS Version: %s", __version__)
×
54
    logging.info("Python Version: %s", sys.version)
×
55
    logging.info("Platform: %s (%s)", platform.platform(), platform.architecture())
×
56
    logging.info("Launching MSColab Server")
×
57
    start_server(APP, sockio, cm, fm)
×
58

59

60
def confirm_action(confirmation_prompt):
1✔
61
    while True:
1✔
62
        confirmation = input(confirmation_prompt).lower()
1✔
63
        if confirmation == "n" or confirmation == "":
1✔
64
            return False
1✔
65
        elif confirmation == "y":
1✔
66
            return True
1✔
67
        else:
68
            print("Invalid input! Please select an option between y or n")
×
69

70

71
def handle_db_reset(verbose=True):
1✔
72
    if mscolab_settings.SQLALCHEMY_DB_URI.startswith("sqlite:///") and (
1✔
73
        db_path := pathlib.Path(mscolab_settings.SQLALCHEMY_DB_URI.removeprefix("sqlite:///"))
74
    ).is_relative_to(mscolab_settings.DATA_DIR):
75
        # Don't remove the database file
76
        # This would be easier if the database wasn't stored in DATA_DIR...
77
        p = pathlib.Path(mscolab_settings.DATA_DIR)
×
78
        for root, dirs, files in os.walk(p, topdown=False):
×
79
            for name in files:
×
80
                full_file_path = pathlib.Path(root) / name
×
81
                if full_file_path != db_path:
×
82
                    full_file_path.unlink()
×
83
            for name in dirs:
×
84
                (pathlib.Path(root) / name).rmdir()
×
85
    elif os.path.exists(mscolab_settings.DATA_DIR):
1✔
86
        shutil.rmtree(mscolab_settings.DATA_DIR)
1✔
87
    create_files()
1✔
88
    flask_migrate.downgrade(directory=migrations.__path__[0], revision="base")
1✔
89
    flask_migrate.upgrade(directory=migrations.__path__[0])
1✔
90
    if verbose is True:
1✔
91
        print("Database has been reset successfully!")
1✔
92

93

94
def handle_db_seed():
1✔
95
    handle_db_reset(verbose=False)
1✔
96
    seed_data()
1✔
97
    print("Database seeded successfully!")
1✔
98

99

100
def handle_mscolab_certificate_init():
1✔
101
    print('generating CRTs for the mscolab server......')
×
102

103
    try:
×
104
        cmd = ["openssl", "req", "-newkey", "rsa:4096", "-keyout",
×
105
               os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "key_mscolab.key"),
106
               "-nodes", "-x509", "-days", "365", "-batch", "-subj",
107
               "/CN=localhost", "-out", os.path.join(mscolab_settings.MSCOLAB_SSO_DIR,
108
                                                     "crt_mscolab.crt")]
109
        subprocess.run(cmd, check=True)
×
110
        logging.info("generated CRTs for the mscolab server.")
×
111
        return True
×
112
    except subprocess.CalledProcessError as error:
×
113
        print(f"Error while generating CRTs for the mscolab server: {error}")
×
114
        return False
×
115

116

117
def handle_local_idp_certificate_init():
1✔
118
    print('generating CRTs for the local identity provider......')
×
119

120
    try:
×
121
        cmd = ["openssl", "req", "-newkey", "rsa:4096", "-keyout",
×
122
               os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "key_local_idp.key"),
123
               "-nodes", "-x509", "-days", "365", "-batch", "-subj",
124
               "/CN=localhost", "-out", os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "crt_local_idp.crt")]
125
        subprocess.run(cmd, check=True)
×
126
        logging.info("generated CRTs for the local identity provider")
×
127
        return True
×
128
    except subprocess.CalledProcessError as error:
×
129
        print(f"Error while generated CRTs for the local identity provider: {error}")
×
130
        return False
×
131

132

133
def handle_mscolab_backend_yaml_init():
1✔
134
    saml_2_backend_yaml_content = """name: Saml2
×
135
config:
136
  entityid_endpoint: true
137
  mirror_force_authn: no
138
  memorize_idp: no
139
  use_memorized_idp_when_force_authn: no
140
  send_requester_id: no
141
  enable_metadata_reload: no
142

143
  # SP Configuration for localhost_test_idp
144
  localhost_test_idp:
145
    name: "MSS Colab Server - Testing IDP(localhost)"
146
    description: "MSS Collaboration Server with Testing IDP(localhost)"
147
    key_file: path/to/key_sp.key # Will be set from the mscolab server
148
    cert_file: path/to/crt_sp.crt # Will be set from the mscolab server
149
    verify_ssl_cert: true # Specifies if the SSL certificates should be verified.
150
    organization: {display_name: Open-MSS, name: Mission Support System, url: 'https://open-mss.github.io/about/'}
151
    contact_person:
152
    - {contact_type: technical, email_address: technical@example.com, given_name: Technical}
153
    - {contact_type: support, email_address: support@example.com, given_name: Support}
154

155
    metadata:
156
      local: [path/to/idp.xml] # Will be set from the mscolab server
157

158
    entityid: http://localhost:5000/proxy_saml2_backend.xml
159
    accepted_time_diff: 60
160
    service:
161
      sp:
162
        ui_info:
163
          display_name:
164
            - lang: en
165
              text: "Open MSS"
166
          description:
167
            - lang: en
168
              text: "Mission Support System"
169
          information_url:
170
            - lang: en
171
              text: "https://open-mss.github.io/about/"
172
          privacy_statement_url:
173
            - lang: en
174
              text: "https://open-mss.github.io/about/"
175
          keywords:
176
            - lang: en
177
              text: ["MSS"]
178
            - lang: en
179
              text: ["OpenMSS"]
180
          logo:
181
            text: "https://open-mss.github.io/assets/logo.png"
182
            width: "100"
183
            height: "100"
184
        authn_requests_signed: true
185
        want_response_signed: true
186
        want_assertion_signed: true
187
        allow_unknown_attributes: true
188
        allow_unsolicited: true
189
        endpoints:
190
          assertion_consumer_service:
191
            - [http://localhost:8083/localhost_test_idp/acs/post, 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']
192
            - [http://localhost:8083/localhost_test_idp/acs/redirect,
193
            'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
194
          discovery_response:
195
          - [<base_url>/<name>/disco, 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol']
196
        name_id_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
197
        name_id_format_allow_create: true
198

199

200
  # # SP Configuration for IDP 2
201
  # sp_config_idp_2:
202
  #   name: "MSS Colab Server - Testing IDP(localhost)"
203
  #   description: "MSS Collaboration Server with Testing IDP(localhost)"
204
  #   key_file: mslib/mscolab/app/key_sp.key
205
  #   cert_file: mslib/mscolab/app/crt_sp.crt
206
  #   organization: {display_name: Open-MSS, name: Mission Support System, url: 'https://open-mss.github.io/about/'}
207
  #   contact_person:
208
  #   - {contact_type: technical, email_address: technical@example.com, given_name: Technical}
209
  #   - {contact_type: support, email_address: support@example.com, given_name: Support}
210

211
  #   metadata:
212
  #     local: [mslib/mscolab/app/idp.xml]
213

214
  #   entityid: http://localhost:5000/proxy_saml2_backend.xml
215
  #   accepted_time_diff: 60
216
  #   service:
217
  #     sp:
218
  #       ui_info:
219
  #         display_name:
220
  #           - lang: en
221
  #             text: "Open MSS"
222
  #         description:
223
  #           - lang: en
224
  #             text: "Mission Support System"
225
  #         information_url:
226
  #           - lang: en
227
  #             text: "https://open-mss.github.io/about/"
228
  #         privacy_statement_url:
229
  #           - lang: en
230
  #             text: "https://open-mss.github.io/about/"
231
  #         keywords:
232
  #           - lang: en
233
  #             text: ["MSS"]
234
  #           - lang: en
235
  #             text: ["OpenMSS"]
236
  #         logo:
237
  #           text: "https://open-mss.github.io/assets/logo.png"
238
  #           width: "100"
239
  #           height: "100"
240
  #       authn_requests_signed: true
241
  #       want_response_signed: true
242
  #       want_assertion_signed: true
243
  #       allow_unknown_attributes: true
244
  #       allow_unsolicited: true
245
  #       endpoints:
246
  #         assertion_consumer_service:
247
  #           - [http://localhost:8083/idp2/acs/post, 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']
248
  #           - [http://localhost:8083/idp2/acs/redirect, 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
249
  #         discovery_response:
250
  #         - [<base_url>/<name>/disco, 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol']
251
  #       name_id_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
252
  #       name_id_format_allow_create: true
253
"""
254
    try:
×
255
        file_path = os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "mss_saml2_backend.yaml")
×
256
        with open(file_path, "w", encoding="utf-8") as file:
×
257
            file.write(saml_2_backend_yaml_content)
×
258
        return True
×
259
    except (FileNotFoundError, PermissionError) as error:
×
260
        print(f"Error while generated backend .yaml for the local mscolabserver: {error}")
×
261
        return False
×
262

263

264
def handle_mscolab_metadata_init(repo_exists):
1✔
265
    """
266
        This will generate necessary metada data file for sso in mscolab through localhost idp
267

268
        Before running this function:
269
        - Ensure that USE_SAML2 is set to True.
270
        - Generate the necessary keys and certificates and configure them in the .yaml
271
        file for the local IDP.
272
    """
273
    print('generating metadata file for the mscolab server')
×
274

275
    try:
×
276
        command = ["python", os.path.join("mslib", "mscolab", "mscolab.py"),
×
277
                   "start"] if repo_exists else ["mscolab", "start"]
278
        process = subprocess.Popen(command)
×
279
        cmd_curl = ["curl", "--retry", "5", "--retry-connrefused", "--retry-delay", "3",
×
280
                    "http://localhost:8083/metadata/localhost_test_idp",
281
                    "-o", os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "metadata_sp.xml")]
282
        subprocess.run(cmd_curl, check=True)
×
283
        process.terminate()
×
284
        logging.info('mscolab metadata file generated succesfully')
×
285
        return True
×
286

287
    except subprocess.CalledProcessError as error:
×
288
        print(f"Error while generating metadata file for the mscolab server: {error}")
×
289
        return False
×
290

291

292
def handle_local_idp_metadata_init(repo_exists):
1✔
293
    print('generating metadata for localhost identity provider')
×
294

295
    try:
×
296
        if os.path.exists(os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "idp.xml")):
×
297
            os.remove(os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "idp.xml"))
×
298

299
        idp_conf_path = os.path.join("mslib", "msidp", "idp_conf.py")
×
300

301
        if not repo_exists:
×
302
            import site
×
303
            site_packages_path = site.getsitepackages()[0]
×
304
            idp_conf_path = os.path.join(site_packages_path, "mslib", "msidp", "idp_conf.py")
×
305

306
        cmd = ["make_metadata", idp_conf_path]
×
307

308
        with open(os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "idp.xml"),
×
309
                  "w", encoding="utf-8") as output_file:
310
            subprocess.run(cmd, stdout=output_file, check=True)
×
311
        logging.info("idp metadata file generated successfully")
×
312
        return True
×
313
    except subprocess.CalledProcessError as error:
×
314
        # Delete the idp.xml file when the subprocess fails
315
        if os.path.exists(os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "idp.xml")):
×
316
            os.remove(os.path.join(mscolab_settings.MSCOLAB_SSO_DIR, "idp.xml"))
×
317
        print(f"Error while generating metadata for localhost identity provider: {error}")
×
318
        return False
×
319

320

321
def handle_sso_crts_init():
1✔
322
    """
323
        This will generate necessary CRTs files for sso in mscolab through localhost idp
324
    """
325
    print("\n\nmscolab sso conf initiating......")
×
326
    if os.path.exists(mscolab_settings.MSCOLAB_SSO_DIR):
×
327
        shutil.rmtree(mscolab_settings.MSCOLAB_SSO_DIR)
×
328
    create_files()
×
329
    if not handle_mscolab_certificate_init():
×
330
        print('Error while handling mscolab certificate.')
×
331
        return
×
332

333
    if not handle_local_idp_certificate_init():
×
334
        print('Error while handling local idp certificate.')
×
335
        return
×
336

337
    if not handle_mscolab_backend_yaml_init():
×
338
        print('Error while handling mscolab backend YAML.')
×
339
        return
×
340

341
    print('\n\nAll CRTs and mscolab backend saml files generated successfully !')
×
342

343

344
def handle_sso_metadata_init(repo_exists):
1✔
345
    print('\n\ngenerating metadata files.......')
×
346
    if not handle_mscolab_metadata_init(repo_exists):
×
347
        print('Error while handling mscolab metadata.')
×
348
        return
×
349

350
    if not handle_local_idp_metadata_init(repo_exists):
×
351
        print('Error while handling idp metadata.')
×
352
        return
×
353

354
    print("\n\nALl necessary metadata files generated successfully")
×
355

356

357
def main():
1✔
358
    parser = argparse.ArgumentParser()
1✔
359
    parser.add_argument("-v", "--version", help="show version", action="store_true", default=False)
1✔
360

361
    subparsers = parser.add_subparsers(help='Available actions', dest='action')
1✔
362

363
    server_parser = subparsers.add_parser("start", help="Start the mscolab server")
1✔
364
    server_parser.add_argument("--debug", help="show debugging log messages on console", action="store_true",
1✔
365
                               default=False)
366
    server_parser.add_argument("--logfile", help="If set to a name log output goes to that file", dest="logfile",
1✔
367
                               default=None)
368

369
    database_parser = subparsers.add_parser("db", help="Manage mscolab database")
1✔
370
    database_parser = database_parser.add_mutually_exclusive_group(required=True)
1✔
371
    database_parser.add_argument("--reset", help="Reset database", action="store_true")
1✔
372
    database_parser.add_argument("--seed", help="Seed database", action="store_true")
1✔
373
    database_parser.add_argument("--users_by_file", type=argparse.FileType('r'),
1✔
374
                                 help="adds users into database, fileformat: suggested_username  name   <email>")
375
    database_parser.add_argument("--delete_users_by_file", type=argparse.FileType('r'),
1✔
376
                                 help="removes users from the database, fileformat: email")
377
    database_parser.add_argument("--default_operation", help="adds all users into a default TEMPLATE operation",
1✔
378
                                 action="store_true")
379
    database_parser.add_argument("--add_all_to_all_operation", help="adds all users into all other operations",
1✔
380
                                 action="store_true")
381
    sso_conf_parser = subparsers.add_parser("sso_conf", help="single sign on process configurations")
1✔
382
    sso_conf_parser = sso_conf_parser.add_mutually_exclusive_group(required=True)
1✔
383
    sso_conf_parser.add_argument("--init_sso_crts",
1✔
384
                                 help="Generate all the essential CRTs required for the Single Sign-On process "
385
                                 "using the local Identity Provider",
386
                                 action="store_true")
387
    sso_conf_parser.add_argument("--init_sso_metadata", help="Generate all the essential metadata files required "
1✔
388
                                 "for the Single Sign-On process using the local Identity Provider",
389
                                 action="store_true")
390

391
    args = parser.parse_args()
1✔
392

393
    if args.version:
1✔
394
        print("***********************************************************************")
1✔
395
        print("\n            Mission Support System (MSS)\n")
1✔
396
        print("***********************************************************************")
1✔
397
        print("Documentation: http://mss.rtfd.io")
1✔
398
        print("Version:", __version__)
1✔
399
        sys.exit()
1✔
400

401
    try:
1✔
402
        _ = git.Repo(os.path.dirname(os.path.realpath(__file__)), search_parent_directories=True)
1✔
403
        repo_exists = True
1✔
404

405
    except git.exc.InvalidGitRepositoryError:
×
406
        repo_exists = False
×
407

408
    if args.action == "start":
1✔
UNCOV
409
        handle_start(args)
×
410

411
    elif args.action == "db":
1✔
412
        if args.reset:
1✔
413
            confirmation = confirm_action("Are you sure you want to reset the database? This would delete "
×
414
                                          "all your data! (y/[n]):")
415
            if confirmation is True:
×
416
                with APP.app_context():
×
417
                    handle_db_reset()
×
418
        elif args.seed:
1✔
419
            confirmation = confirm_action("Are you sure you want to seed the database? Seeding will delete all your "
×
420
                                          "existing data and replace it with seed data (y/[n]):")
421
            if confirmation is True:
×
422
                with APP.app_context():
×
423
                    handle_db_seed()
×
424
        elif args.users_by_file is not None:
1✔
425
            # fileformat: suggested_username  name   <email>
426
            confirmation = confirm_action("Are you sure you want to add users to the database? (y/[n]):")
×
427
            if confirmation is True:
×
428
                for line in args.users_by_file.readlines():
×
429
                    info = line.split()
×
430
                    username = info[0]
×
431
                    emailid = info[-1][1:-1]
×
432
                    password = secrets.token_hex(8)
×
433
                    add_user(emailid, username, password)
×
434
        elif args.default_operation:
1✔
435
            confirmation = confirm_action(
×
436
                "Are you sure you want to add users to the default TEMPLATE operation? (y/[n]):")
437
            if confirmation is True:
×
438
                # adds all users as collaborator on the operation TEMPLATE if not added, command can be repeated
439
                add_all_users_default_operation(access_level='admin')
×
440
        elif args.add_all_to_all_operation:
1✔
441
            confirmation = confirm_action(
×
442
                "Are you sure you want to add users to the ALL operations? (y/[n]):")
443
            if confirmation is True:
×
444
                # adds all users to all Operations
445
                add_all_users_to_all_operations()
×
446
        elif args.delete_users_by_file:
1✔
447
            confirmation = confirm_action(
×
448
                "Are you sure you want to delete a user? (y/[n]):")
449
            if confirmation is True:
×
450
                # deletes users from the db
451
                for email in args.delete_users_by_file.readlines():
×
452
                    delete_user(email.strip())
×
453

454
    elif args.action == "sso_conf":
×
455
        if args.init_sso_crts:
×
456
            confirmation = confirm_action(
457
                "This will reset and initiation all CRTs and SAML yaml file as default. "
458
                "Are you sure to continue? (y/[n]):")
459
            if confirmation is True:
×
460
                handle_sso_crts_init()
×
461
        if args.init_sso_metadata:
×
462
            confirmation = confirm_action(
×
463
                "Are you sure you executed --init_sso_crts before running this? (y/[n]):")
464
            if confirmation is True:
×
465
                confirmation = confirm_action(
×
466
                    """
467
                    This will generate necessary metada data file for sso in mscolab through localhost idp
468

469
                    Before running this function:
470
                    - Ensure that USE_SAML2 is set to True.
471
                    - Generate the necessary keys and certificates and configure them in the .yaml
472
                    file for the local IDP.
473

474
                    Are you sure you set all correctly as per the documentation? (y/[n]):
475
                    """
476
                )
477
                if confirmation is True:
×
478
                    handle_sso_metadata_init(repo_exists)
×
479

480

481
if __name__ == '__main__':
482
    main()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc