diff --git a/SAS/TMSS/src/remakemigrations.py b/SAS/TMSS/src/remakemigrations.py index 015794aac00b3b4d6890e23f8cc402ef008d330e..9decd35f5d7e8d193060f2528c332df7bedd8a61 100755 --- a/SAS/TMSS/src/remakemigrations.py +++ b/SAS/TMSS/src/remakemigrations.py @@ -41,7 +41,7 @@ def delete_old_migrations(): for f in [path for path in files if ("auto" in path or "populate" in path)]: logger.info('Deleting: %s' % f) os.remove(f) - execute_and_log('svn rm %s' % f) + execute_and_log('git rm %s' % f) def make_django_migrations(): @@ -80,7 +80,7 @@ def put_migrations_under_version_control(): logger.info('Putting migrations under version control...') files = glob_migrations() for f in files: - execute_and_log('svn add %s' % f) + execute_and_log('git add %s' % f) def remake_migrations(): @@ -98,4 +98,4 @@ if __name__ == "__main__": handler.setLevel(logging.INFO) logger.addHandler(handler) - remake_migrations() \ No newline at end of file + remake_migrations() diff --git a/SAS/TMSS/src/tmss/settings.py b/SAS/TMSS/src/tmss/settings.py index 2e90189375ce39a426016f832845165dbd8e55e2..060a1991b97b4a16d3a681d4b54110d1b4349334 100644 --- a/SAS/TMSS/src/tmss/settings.py +++ b/SAS/TMSS/src/tmss/settings.py @@ -112,6 +112,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'mozilla_django_oidc.middleware.SessionRefresh', ] ROOT_URLCONF = 'tmss.urls' @@ -174,9 +175,9 @@ DATABASES = { REST_FRAMEWORK = { # Authentication 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.BasicAuthentication', - 'rest_framework.authentication.SessionAuthentication', 'mozilla_django_oidc.contrib.drf.OIDCAuthentication', + 'rest_framework.authentication.SessionAuthentication', + #'rest_framework.authentication.BasicAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': [ #'rest_framework.permissions.AllowAny', @@ -221,16 +222,28 @@ AUTH_LDAP_USER_ATTR_MAP = { # OPEN-ID CONNECT -OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID', 'id') # Secret, do not put real credentials on Git -OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET', 'secret') # Secret, do not put real credentials on Git -OIDC_OP_AUTHORIZATION_ENDPOINT = "https://localhost:9090/authentication" -OIDC_OP_TOKEN_ENDPOINT = "https://localhost:9090/token" -OIDC_OP_USER_ENDPOINT = "https://localhost:9090/userinfo" +OIDC_DRF_AUTH_BACKEND = 'mozilla_django_oidc.auth.OIDCAuthenticationBackend' + +# For talking to PYOP Identity Provider: +#OIDC_VERIFY_SSL = False # TODO: Remove for production! +#OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID', 'a0PF52uaIQxu') # Secret, do not put real credentials on Git +#OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET', '93863d5aad044c0baaf1f0021b2542db') # Secret, do not put real credentials on Git +#OIDC_OP_AUTHORIZATION_ENDPOINT = "https://localhost:9090/authentication" +#OIDC_OP_TOKEN_ENDPOINT = "https://localhost:9090/token" +#OIDC_OP_USER_ENDPOINT = "https://localhost:9090/userinfo" + + +# For talking to Mozilla Identity Provider: +OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID', '1') # Secret, do not put real credentials on Git +OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET', 'secret') # Secret, do not put real credentials on Git +OIDC_OP_AUTHORIZATION_ENDPOINT="http://localhost:8080/openid/authorize" +OIDC_OP_TOKEN_ENDPOINT="http://localhost:8080/openid/token" +OIDC_OP_USER_ENDPOINT="http://localhost:8080/openid/userinfo" AUTHENTICATION_BACKENDS = ( #'django_auth_ldap.backend.LDAPBackend', - #'django.contrib.auth.backends.ModelBackend', 'mozilla_django_oidc.auth.OIDCAuthenticationBackend', + 'django.contrib.auth.backends.ModelBackend', ) # Password validation diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20200123_1057.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20200123_1057.py new file mode 100644 index 0000000000000000000000000000000000000000..1bccdfe6ac7fbf01c71031f992938c4e25aca38d --- /dev/null +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20200123_1057.py @@ -0,0 +1,933 @@ +# Generated by Django 2.0.6 on 2020-01-23 10:57 + +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +import django.contrib.postgres.indexes +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('tmssapp', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Algorithm', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='AntennaSet', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('rcus', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), size=128)), + ('inputs', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, size=128)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Cluster', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('location', models.CharField(help_text='Human-readable location of the cluster.', max_length=128)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CopyReason', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Cycle', + fields=[ + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128, primary_key=True, serialize=False)), + ('start', models.DateTimeField(help_text='Moment at which the cycle starts, that is, when its projects can run.')), + ('stop', models.DateTimeField(help_text='Moment at which the cycle officially ends.')), + ('number', models.IntegerField(help_text='Cycle number.')), + ('standard_hours', models.IntegerField(help_text='Number of offered hours for standard observations.')), + ('expert_hours', models.IntegerField(help_text='Number of offered hours for expert observations.')), + ('filler_hours', models.IntegerField(help_text='Number of offered hours for filler observations.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Dataformat', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Dataproduct', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('filename', models.CharField(help_text='Name of the file (or top-level directory) of the dataproduct. Adheres to a naming convention, but is not meant for parsing.', max_length=128)), + ('directory', models.CharField(help_text='Directory where this dataproduct is (to be) stored.', max_length=1024)), + ('deleted_since', models.DateTimeField(help_text='When this dataproduct was removed from disk, or NULL if not deleted (NULLable).', null=True)), + ('pinned_since', models.DateTimeField(help_text='When this dataproduct was pinned to disk, that is, forbidden to be removed, or NULL if not pinned (NULLable).', null=True)), + ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Dataproduct properties (f.e. beam, subband), to distinguish them when produced by the same task, and to act as input for selections in the Task Input and Work Request Relation Blueprint objects.')), + ('do_cancel', models.DateTimeField(help_text='When this dataproduct was cancelled (NULLable). Cancelling a dataproduct triggers cleanup if necessary.', null=True)), + ('expected_size', models.BigIntegerField(help_text='Expected size of dataproduct size, in bytes. Used for scheduling purposes. NULL if size is unknown (NULLable).', null=True)), + ('size', models.BigIntegerField(help_text='Dataproduct size, in bytes. Used for accounting purposes. NULL if size is (yet) unknown (NULLable).', null=True)), + ('feedback_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Dataproduct properties, as reported by the producing process.')), + ('dataformat', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Dataformat')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DataproductArchiveInfo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('storage_ticket', models.CharField(help_text='Archive-system identifier.', max_length=128)), + ('public_since', models.DateTimeField(help_text='Dataproduct is available for public download since this moment, or NULL if dataproduct is not (NULLable).', null=True)), + ('corrupted_since', models.DateTimeField(help_text='Earliest timestamp from which this dataproduct is known to be partially or fully corrupt, or NULL if dataproduct is not known to be corrupt (NULLable).', null=True)), + ('dataproduct', models.ForeignKey(help_text='A dataproduct residing in the archive.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Dataproduct')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DataproductFeedbackTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DataproductHash', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('hash', models.CharField(help_text='Hash value.', max_length=128)), + ('algorithm', models.ForeignKey(help_text='Algorithm used (MD5, AES256).', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Algorithm')), + ('dataproduct', models.ForeignKey(help_text='The dataproduct to which this hash refers.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Dataproduct')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DataproductSpecificationsTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DataproductTransform', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('identity', models.BooleanField(help_text='TRUE if this transform only copies, tars, or losslessly compresses its input, FALSE if the transform changes the data. Allows for efficient reasoning about data duplication.')), + ('input', models.ForeignKey(help_text='A dataproduct that was the input of a transformation.', on_delete=django.db.models.deletion.PROTECT, related_name='inputs', to='tmssapp.Dataproduct')), + ('output', models.ForeignKey(help_text='A dataproduct that was produced from the input dataproduct.', on_delete=django.db.models.deletion.PROTECT, related_name='outputs', to='tmssapp.Dataproduct')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Datatype', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DefaultDataproductSpecificationsTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(max_length=128, unique=True)), + ('template', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.DataproductSpecificationsTemplate')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DefaultGeneratorTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(max_length=128, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DefaultSchedulingUnitTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(max_length=128, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DefaultSubtaskTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(max_length=128, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DefaultTaskTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(max_length=128, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DefaultWorkRelationSelectionTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(max_length=128, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Filesystem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('capacity', models.BigIntegerField(help_text='Capacity in bytes')), + ('cluster', models.ForeignKey(help_text='Cluster hosting this filesystem.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Cluster')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='GeneratorTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ('create_function', models.CharField(help_text='Python function to call to execute the generator.', max_length=128)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Project', + fields=[ + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128, primary_key=True, serialize=False)), + ('priority', models.IntegerField(default=0, help_text='Priority of this project w.r.t. other projects. Projects can interrupt observations of lower-priority projects.')), + ('can_trigger', models.BooleanField(default=False, help_text='True if this project is allowed to supply observation requests on the fly, possibly interrupting currently running observations (responsive telescope).')), + ('private_data', models.BooleanField(default=True, help_text='True if data of this project is sensitive. Sensitive data is not made public.')), + ('expert', models.BooleanField(default=False, help_text='Expert projects put more responsibility on the PI.')), + ('filler', models.BooleanField(default=False, help_text='Use this project to fill up idle telescope time.')), + ('cycle', models.ForeignKey(help_text='Cycle(s) to which this project belongs (NULLable).', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='projects', to='tmssapp.Cycle')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Role', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ScheduleMethod', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SchedulingSet', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('generator_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Parameters for the generator (NULLable).', null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SchedulingUnitBlueprint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('requirements_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Scheduling and/or quality requirements for this scheduling unit (IMMUTABLE).')), + ('do_cancel', models.BooleanField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SchedulingUnitDraft', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('requirements_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Scheduling and/or quality requirements for this run.')), + ('generator_instance_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Parameter value that generated this run draft (NULLable).', null=True)), + ('copies', models.ForeignKey(help_text='Source reference, if we are a copy (NULLable).', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='copied_from', to='tmssapp.SchedulingUnitDraft')), + ('copy_reason', models.ForeignKey(help_text='Reason why source was copied (NULLable).', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.CopyReason')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SchedulingUnitTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='StationType', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Subtask', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('start_time', models.DateTimeField(help_text='Start this subtask at the specified time (NULLable).', null=True)), + ('stop_time', models.DateTimeField(help_text='Stop this subtask at the specified time (NULLable).', null=True)), + ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Final specifications, as input for the controller.')), + ('do_cancel', models.DateTimeField(help_text='Timestamp when the subtask has been ordered to cancel (NULLable).', null=True)), + ('priority', models.IntegerField(help_text='Absolute priority of this subtask (higher value means more important).')), + ('scheduler_input_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Partial specifications, as input for the scheduler.')), + ('cluster', models.ForeignKey(help_text='Where the Subtask is scheduled to run (NULLable).', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Cluster')), + ('schedule_method', models.ForeignKey(help_text='Which method to use for scheduling this Subtask. One of (MANUAL, BATCH, DYNAMIC).', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.ScheduleMethod')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskConnector', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('dataformats', models.ManyToManyField(blank=True, to='tmssapp.Dataformat')), + ('datatype', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Datatype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskInput', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('selection_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Filter to apply to the dataproducts of the producer, to derive input dataproducts when scheduling.')), + ('connector', models.ForeignKey(help_text='Which connector this Task Input implements.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='tmssapp.SubtaskConnector')), + ('dataproducts', models.ManyToManyField(help_text='The Dataproducts resulting from application of the filter at time of scheduling Although the dataproducts are simply the result of applying the filter on immutable data, the filter application could change over time. We thus store the result of this filtering directly to retain which input was specified for the task..', to='tmssapp.Dataproduct')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskInputSelectionTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskOutput', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('connector', models.ForeignKey(help_text='Which connector this Subtask Output implements.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='tmssapp.SubtaskConnector')), + ('subtask', models.ForeignKey(help_text='Subtask to which this output specification refers.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.Subtask')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskState', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ('queue', models.BooleanField(default=False)), + ('realtime', models.BooleanField(default=False)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SubtaskType', + fields=[ + ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Tags', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=128)), + ('description', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='TaskBlueprint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schedulings for this task (IMMUTABLE).')), + ('do_cancel', models.BooleanField(help_text='Cancel this task.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TaskConnectors', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('dataformats', models.ManyToManyField(blank=True, to='tmssapp.Dataformat')), + ('datatype', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Datatype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TaskDraft', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Specifications for this task.')), + ('copies', models.ForeignKey(help_text='Source reference, if we are a copy (NULLable).', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='copied_from', to='tmssapp.TaskDraft')), + ('copy_reason', models.ForeignKey(help_text='Reason why source was copied (NULLable).', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.CopyReason')), + ('scheduling_unit_draft', models.ForeignKey(help_text='Scheduling Unit draft to which this task draft belongs.', on_delete=django.db.models.deletion.CASCADE, related_name='task_drafts', to='tmssapp.SchedulingUnitDraft')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TaskRelationBlueprint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('selection_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Filter for selecting dataproducts from the output role.')), + ('consumer', models.ForeignKey(help_text='Task Blueprint that has the input connector.', on_delete=django.db.models.deletion.CASCADE, related_name='consumed_by', to='tmssapp.TaskBlueprint')), + ('dataformat', models.ForeignKey(help_text='Selected data format to use.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Dataformat')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TaskRelationDraft', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('selection_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Filter for selecting dataproducts from the output role.')), + ('consumer', models.ForeignKey(help_text='Task Draft that has the input connector.', on_delete=django.db.models.deletion.CASCADE, related_name='consumed_by', to='tmssapp.TaskDraft')), + ('dataformat', models.ForeignKey(help_text='Selected data format to use. One of (MS, HDF5).', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Dataformat')), + ('input', models.ForeignKey(help_text='Input connector of consumer.', on_delete=django.db.models.deletion.CASCADE, related_name='inputs_task_relation_draft', to='tmssapp.TaskConnectors')), + ('output', models.ForeignKey(help_text='Output connector of producer.', on_delete=django.db.models.deletion.CASCADE, related_name='outputs_task_relation_draft', to='tmssapp.TaskConnectors')), + ('producer', models.ForeignKey(help_text='Task Draft that has the output connector. NOTE: The producer does typically, but not necessarily, belong to the same Scheduling Unit (or even the same Project) as the consumer.', on_delete=django.db.models.deletion.CASCADE, related_name='produced_by', to='tmssapp.TaskDraft')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TaskTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ('validation_code_js', models.CharField(help_text='JavaScript code for additional (complex) validation.', max_length=128)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WorkRelationSelectionTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, help_text='User-defined search keywords for object.', size=8)), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), + ('description', models.CharField(help_text='A longer description of this object.', max_length=255)), + ('version', models.CharField(help_text='Version of this template (with respect to other templates of the same name).', max_length=128)), + ('schema', django.contrib.postgres.fields.jsonb.JSONField(help_text='Schema for the configurable parameters needed to use this template.')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='taskrelationdraft', + name='selection_template', + field=models.ForeignKey(help_text='Schema used for selection_doc.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.WorkRelationSelectionTemplate'), + ), + migrations.AddField( + model_name='taskrelationblueprint', + name='draft', + field=models.ForeignKey(help_text='Task Relation Draft which this work request instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='related_task_relation_blueprint', to='tmssapp.TaskRelationDraft'), + ), + migrations.AddField( + model_name='taskrelationblueprint', + name='input', + field=models.ForeignKey(help_text='Input connector of consumer.', on_delete=django.db.models.deletion.CASCADE, related_name='inputs_task_relation_blueprint', to='tmssapp.TaskConnectors'), + ), + migrations.AddField( + model_name='taskrelationblueprint', + name='output', + field=models.ForeignKey(help_text='Output connector of producer.', on_delete=django.db.models.deletion.CASCADE, related_name='outputs_task_relation_blueprint', to='tmssapp.TaskConnectors'), + ), + migrations.AddField( + model_name='taskrelationblueprint', + name='producer', + field=models.ForeignKey(help_text='Task Blueprint that has the output connector.', on_delete=django.db.models.deletion.CASCADE, related_name='produced_by', to='tmssapp.TaskBlueprint'), + ), + migrations.AddField( + model_name='taskrelationblueprint', + name='selection_template', + field=models.ForeignKey(help_text='Schema used for selection_doc.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.WorkRelationSelectionTemplate'), + ), + migrations.AddField( + model_name='taskdraft', + name='specifications_template', + field=models.ForeignKey(help_text='Schema used for requirements_doc.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.TaskTemplate'), + ), + migrations.AddField( + model_name='taskconnectors', + name='input_of', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inputs', to='tmssapp.TaskTemplate'), + ), + migrations.AddField( + model_name='taskconnectors', + name='output_of', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='tmssapp.TaskTemplate'), + ), + migrations.AddField( + model_name='taskconnectors', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Role'), + ), + migrations.AddField( + model_name='taskblueprint', + name='draft', + field=models.ForeignKey(help_text='Task Draft which this task instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='related_task_blueprint', to='tmssapp.TaskDraft'), + ), + migrations.AddField( + model_name='taskblueprint', + name='scheduling_unit_blueprint', + field=models.ForeignKey(help_text='Scheduling Unit Blueprint to which this task belongs.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.SchedulingUnitBlueprint'), + ), + migrations.AddField( + model_name='taskblueprint', + name='specifications_template', + field=models.ForeignKey(help_text='Schema used for specifications_doc (IMMUTABLE).', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.TaskTemplate'), + ), + migrations.AddField( + model_name='subtasktemplate', + name='type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SubtaskType'), + ), + migrations.AddField( + model_name='subtaskinput', + name='producer', + field=models.ForeignKey(help_text='The Subtask Output providing the input dataproducts.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SubtaskOutput'), + ), + migrations.AddField( + model_name='subtaskinput', + name='selection_template', + field=models.ForeignKey(help_text='Schema used for selection_doc.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SubtaskInputSelectionTemplate'), + ), + migrations.AddField( + model_name='subtaskinput', + name='subtask', + field=models.ForeignKey(help_text='Subtask to which this input specification refers.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.Subtask'), + ), + migrations.AddField( + model_name='subtaskinput', + name='task_relation_blueprint', + field=models.ForeignKey(help_text='Task Relation Blueprint which this Subtask Input implements (NULLable).', null=True, on_delete=django.db.models.deletion.SET_NULL, to='tmssapp.TaskRelationBlueprint'), + ), + migrations.AddField( + model_name='subtaskconnector', + name='input_of', + field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='outputs', to='tmssapp.SubtaskTemplate'), + ), + migrations.AddField( + model_name='subtaskconnector', + name='output_of', + field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='inputs', to='tmssapp.SubtaskTemplate'), + ), + migrations.AddField( + model_name='subtaskconnector', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.Role'), + ), + migrations.AddField( + model_name='subtask', + name='specifications_template', + field=models.ForeignKey(help_text='Schema used for specifications_doc.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SubtaskTemplate'), + ), + migrations.AddField( + model_name='subtask', + name='state', + field=models.ForeignKey(help_text='Subtask state (see Subtask State Machine).', on_delete=django.db.models.deletion.PROTECT, related_name='task_states', to='tmssapp.SubtaskState'), + ), + migrations.AddField( + model_name='subtask', + name='task_blueprint', + field=models.ForeignKey(help_text='Task Blueprint to which this Subtask belongs.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subtasks', to='tmssapp.TaskBlueprint'), + ), + migrations.AddField( + model_name='schedulingunitdraft', + name='requirements_template', + field=models.ForeignKey(help_text='Schema used for requirements_doc.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.SchedulingUnitTemplate'), + ), + migrations.AddField( + model_name='schedulingunitdraft', + name='scheduling_set', + field=models.ForeignKey(help_text='Set to which this scheduling unit draft belongs.', on_delete=django.db.models.deletion.CASCADE, related_name='scheduling_unit_drafts', to='tmssapp.SchedulingSet'), + ), + migrations.AddField( + model_name='schedulingunitblueprint', + name='draft', + field=models.ForeignKey(help_text='Scheduling Unit Draft which this run instantiates.', on_delete=django.db.models.deletion.CASCADE, related_name='related_scheduling_unit_blueprint', to='tmssapp.SchedulingUnitDraft'), + ), + migrations.AddField( + model_name='schedulingunitblueprint', + name='requirements_template', + field=models.ForeignKey(help_text='Schema used for requirements_doc (IMMUTABLE).', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.SchedulingUnitTemplate'), + ), + migrations.AddField( + model_name='schedulingset', + name='generator_source', + field=models.ForeignKey(help_text='Reference for the generator to an existing collection of specifications (NULLable).', null=True, on_delete=django.db.models.deletion.SET_NULL, to='tmssapp.SchedulingUnitDraft'), + ), + migrations.AddField( + model_name='schedulingset', + name='generator_template', + field=models.ForeignKey(help_text='Generator for the scheduling units in this set (NULLable).', null=True, on_delete=django.db.models.deletion.SET_NULL, to='tmssapp.GeneratorTemplate'), + ), + migrations.AddField( + model_name='schedulingset', + name='project', + field=models.ForeignKey(help_text='Project to which this scheduling set belongs.', on_delete=django.db.models.deletion.PROTECT, related_name='scheduling_sets', to='tmssapp.Project'), + ), + migrations.AddField( + model_name='defaultworkrelationselectiontemplate', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.WorkRelationSelectionTemplate'), + ), + migrations.AddField( + model_name='defaulttasktemplate', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.TaskTemplate'), + ), + migrations.AddField( + model_name='defaultsubtasktemplate', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SubtaskTemplate'), + ), + migrations.AddField( + model_name='defaultschedulingunittemplate', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SchedulingUnitTemplate'), + ), + migrations.AddField( + model_name='defaultgeneratortemplate', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.GeneratorTemplate'), + ), + migrations.AddField( + model_name='dataproduct', + name='feedback_template', + field=models.ForeignKey(help_text='Schema used for feedback_doc.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.DataproductFeedbackTemplate'), + ), + migrations.AddField( + model_name='dataproduct', + name='producer', + field=models.ForeignKey(help_text='Subtask Output which generates this dataproduct.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SubtaskOutput'), + ), + migrations.AddField( + model_name='dataproduct', + name='specifications_template', + field=models.ForeignKey(help_text='Schema used for specifications_doc.', on_delete=django.db.models.deletion.CASCADE, to='tmssapp.DataproductSpecificationsTemplate'), + ), + migrations.AddField( + model_name='antennaset', + name='station_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tmssapp.StationType'), + ), + migrations.AddIndex( + model_name='taskrelationdraft', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_aeef84_gin'), + ), + migrations.AddIndex( + model_name='taskrelationblueprint', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_256437_gin'), + ), + migrations.AddIndex( + model_name='taskconnectors', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_0ebd6d_gin'), + ), + migrations.AddIndex( + model_name='subtaskoutput', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_e25b4c_gin'), + ), + migrations.AddIndex( + model_name='subtaskinput', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_fb9960_gin'), + ), + migrations.AddIndex( + model_name='subtaskconnector', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_60e299_gin'), + ), + migrations.AddIndex( + model_name='subtask', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_d2fc43_gin'), + ), + migrations.AddIndex( + model_name='defaultworkrelationselectiontemplate', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_b652d9_gin'), + ), + migrations.AddIndex( + model_name='defaulttasktemplate', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_c88200_gin'), + ), + migrations.AddIndex( + model_name='defaultsubtasktemplate', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_e9c73d_gin'), + ), + migrations.AddIndex( + model_name='defaultschedulingunittemplate', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_3ab2d6_gin'), + ), + migrations.AddIndex( + model_name='defaultgeneratortemplate', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_89c89d_gin'), + ), + migrations.AddIndex( + model_name='defaultdataproductspecificationstemplate', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_269b1f_gin'), + ), + migrations.AddIndex( + model_name='dataproducttransform', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_380c1f_gin'), + ), + migrations.AddIndex( + model_name='dataproducthash', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_dae145_gin'), + ), + migrations.AddIndex( + model_name='dataproductarchiveinfo', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_ebf2ef_gin'), + ), + migrations.AddIndex( + model_name='dataproduct', + index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_5932a3_gin'), + ), + ] diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py index 7dbb6053394aa14a436f508a58abec3dfd5251bd..8fcea39a130ebe08b53939653c6465d3343d71d5 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py @@ -6,7 +6,7 @@ from ..populate import * class Migration(migrations.Migration): dependencies = [ - ('tmssapp', '0002_auto_20190606_1535'), + ('tmssapp', '0002_auto_20200123_1057'), ] operations = [ migrations.RunPython(populate_choices) ] diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/.circleci/config.yml b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/.circleci/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..7c2b7c3c55992eb980b14c96eaa1a879ef170369 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/.circleci/config.yml @@ -0,0 +1,100 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 +jobs: + build: + docker: + - image: mozilla/cidockerbases:docker-latest + working_directory: / + + steps: + - run: + name: Get info + command: uname -v + + - setup_remote_docker + + - run: + name: Get info + command: docker info + + - run: + name: Install essential packages + command: apt-get install make + + - checkout: + path: /oidc_testprovider + + - run: + name: Build Docker images + working_directory: /oidc_testprovider + command: | + make build + + - run: + name: Push to Dockerhub on tag + working_directory: /oidc_testprovider + command: | + function retry { + set +e + local n=0 + local max=3 + while true; do + "$@" && break || { + if [[ $n -lt $max ]]; then + ((n++)) + echo "Command failed. Attempt $n/$max:" + else + echo "Failed after $n attempts." + exit 1 + fi + } + done + set -e + } + + # Namespace on dockerhub to push: + # https://hub.docker.com/u/mozilla/oidc-testprovider + export DOCKER_NAMESPACE=mozilla/oidc-testprovider + export IMAGES=(oidc_e2e_setup_py2 oidc_e2e_setup_py3 oidc_testprovider oidc_testrp_py2 oidc_testrp_py3 oidc_testrunner) + + # If a tag was pushed to github, push tagged images and latest + # images to Dockerhub + if [ -n "${CIRCLE_TAG}" ]; then + # Log into Dockerhub + echo "${DOCKER_PASS}" | docker login -u="${DOCKER_USER}" --password-stdin + + for IMAGE in "${IMAGES[@]}" + do + echo "" + echo ">>> WORKING ON ${IMAGE}..." + echo "" + # Tag and push tagged image. + retry docker tag "${IMAGE}:latest" "${DOCKER_NAMESPACE}:${IMAGE}-${CIRCLE_TAG}" + retry docker push "${DOCKER_NAMESPACE}:${IMAGE}-${CIRCLE_TAG}" + + # Tag and push latest image. + retry docker tag "${IMAGE}:latest" "${DOCKER_NAMESPACE}:${IMAGE}-latest" + retry docker push "${DOCKER_NAMESPACE}:${IMAGE}-latest" + done + fi + +workflows: + version: 2 + + # workflow jobs are _not_ run in tag builds by default + # we use filters to whitelist jobs that should be run for tags + + # workflow jobs are run in _all_ branch builds by default + # we use filters to blacklist jobs that shouldn't be run for a branch + + # see: https://circleci.com/docs/2.0/workflows/#git-tag-job-execution + + build-test-push: + jobs: + - build: + filters: + tags: + only: /.*/ diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/HISTORY.md b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/HISTORY.md new file mode 100644 index 0000000000000000000000000000000000000000..4d8d62422483fd93d04159b64dcc376cec4ae539 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/HISTORY.md @@ -0,0 +1,35 @@ +HISTORY +======= + +v0.9.3 (October 23rd, 2019) +--------------------------- + +Bugs: + +* fix docker push code + + +v0.9.2 (October 22nd, 2019) +--------------------------- + +No substantive changes. Doing a new tag so as to push images to dockerhub. + + +v0.9.1 (October 22nd, 2019) +--------------------------- + +Bugs: + +* fix `build` and `pull` rules in Makefile to use the correct tags + + +v0.9.0 (October 22nd, 2019) +--------------------------- + +First tagged release. + +Features: + +* new `createuser` command in `oidc_testprovider` image +* redid how images are tagged and we're now pushing them to dockerhub + in the `mozilla` user diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/Makefile b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..091744b79538494207e8499c5d2ab1d9a57d49d4 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/Makefile @@ -0,0 +1,33 @@ +DEFAULT_GOAL := help + +NS ?= mozilla/oidc-testprovider +IMAGES := oidc_testprovider oidc_testrunner oidc_testrp_py2 oidc_testrp_py3 oidc_e2e_setup_py2 oidc_e2e_setup_py3 +BUILD := $(addprefix build-,${IMAGES}) +PULL := $(addprefix pull-,$(IMAGES)) +CLEAN := $(addprefix clean-,$(IMAGES)) + +.PHONY: help +help: + @fgrep -h "##" Makefile | fgrep -v fgrep | sed 's/\(.*\):.*##/\1:/' + +.PHONY: build +build: ${BUILD} ## Build all images + +.PHONY: pull +pull: ${PULL} ## Pull all -latest images + +.PHONY: clean +clean: ${CLEAN} ## Clean images and other artifacts + +.PHONY: ${BUILD} +${BUILD}: build-%: + docker build -t $* -f dockerfiles/$* . + +.PHONY: ${PULL} +${PULL}: pull-%: + docker pull ${NS}:$*-latest + +.PHONY: ${CLEAN} +${CLEAN}: clean-%: + docker rmi ${NS}/$(subst _py,:py,$(*)) + docker rmi $(subst _py,:py,$(*)) diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/README.md b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9037b454463959b2a99f01058e314f7d0d8de937 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/README.md @@ -0,0 +1,98 @@ +# docker-test-mozilla-django-oidc + +The purpose of these docker images is to setup a local environment to develop and test +`mozilla-django-oidc`. + + +## oidc-testprovider + +https://hub.docker.com/r/mozilla/oidc-testprovider/tags?name=testprovider + +* Provides a docker image for an OIDC OP with preconfigured OIDC client IDs and secrets +* OIDC provider endpoint is exposed in port `8080` +* Provides a Django management command for creating users +* Uses `django-oidc-provider` + + +### Usage + +In order for this setup to work `testprovider`, `testrp` hostnames should resolve to the +IP of the docker image (for local development it's `127.0.0.1`). + +You can add the resolution to your `/etc/hosts` file. + +You can also use [nip.io](http://nip.io/). For example, if you name the service +"oidcprovider", then you could have these three variables: + +``` +OIDC_OP_AUTHORIZATION_ENDPOINT=http://oidcprovider.127.0.0.1.nip.io:8080/openid/authorize +OIDC_OP_TOKEN_ENDPOINT=http://oidcprovider.127.0.0.1.nip.io:8080/openid/token +OIDC_OP_USER_ENDPOINT=http://oidcprovider.127.0.0.1.nip.io:8080/openid/userinfo +``` + +### Example setup + +`docker-compose.yml` + +``` +version: '3' +services: + testprovider: + image: mozilla/oidc-testprovider:oidc_testprovider-v0.9.3 + ports: + - "8080:8080" +``` + + +### Creating users in the container + +The `testprovider` image has a Django management command for creating users in +the OIDC provider. This lets you create users on the command line. + +With an already running `testprovider` container run: + +``` +docker-compose exec testprovider manage.py createuser USERNAME PASSWORD EMAIL +``` + + +## Other images + +All images are pushed to: https://hub.docker.com/r/mozilla/oidc-testprovider + +* `oidc_testprovider` (See above) +* `oidc_testrunner` +* `oidc_testrp_py{2,3}` + * Test django project preconfigured to work with `testprovider` + * Uses `mozilla-django-oidc` as an authentication backend + * Test RP is exposed in port `8081` + * Builds based in both python 2/3 + * Environment variables + * `TEST_OIDC_ALGO={hs,rs}` +* `oidc_e2e_setup_py{2,3}` + * Dockerized setup for e2e testing of mozilla-django-oidc + + +### Example setup for oidc_testrp + +`docker-compose.yml` + +``` +version: '3' +services: + testrp: + image: mozilla/oidc-testprovider:oidc_testrp_py3-v0.9.3 + ports: + - "8081:8081" + environment: + - TEST_OIDC_ALGO=hs +``` + +## Development + +We use `make` to automate the docker image workflow. + +For more info run `make help`. + +Pushing a tag to GitHub will trigger building images and uploading them +to Dockerhub. diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/docker-compose.yml b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..a1022994588976175c6169fee1051c60b7b89cdf --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3' +services: + testprovider: + image: mozilla/oidc-testprovider:oidc_testprovider-v0.9.3 + ports: + - "8080:8080" diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py2 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py2 new file mode 100644 index 0000000000000000000000000000000000000000..ac0e6a7bf389c7c6cb802137e8544f889b863853 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py2 @@ -0,0 +1,29 @@ +FROM python:2-stretch + +EXPOSE 8080 8081 + +RUN pip install virtualenv +RUN virtualenv /testrp_env +RUN virtualenv /testprovider_env + +COPY testprovider /testprovider/ +COPY testrp /testrp/ + +RUN . /testprovider_env/bin/activate && pip install -r /testprovider/requirements.txt +RUN . /testrp_env/bin/activate && pip install -r /testrp/requirements.txt + +# Install python and python dependencies +RUN apt-get update && \ + apt-get install -y wait-for-it + +# Install firefox +RUN apt-get install -y --no-install-recommends firefox-esr && \ + wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" -O /tmp/firefox.tar.bz2 && \ + tar xvf /tmp/firefox.tar.bz2 -C /opt && \ + rm /usr/bin/firefox && \ + ln -s /opt/firefox/firefox /usr/bin/firefox + +# Install geckodriver +RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux32.tar.gz" -O /tmp/geckodriver.tar.gz && \ + tar xvf /tmp/geckodriver.tar.gz -C /opt && \ + ln -s /opt/geckodriver /usr/bin/geckodriver diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py3 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py3 new file mode 100644 index 0000000000000000000000000000000000000000..9ed6e566c6b88dd21d5782d7e1b477290042b501 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py3 @@ -0,0 +1,29 @@ +FROM python:3.6-stretch + +EXPOSE 8080 8081 + +RUN pip install virtualenv +RUN virtualenv /testrp_env +RUN virtualenv /testprovider_env + +COPY testprovider /testprovider/ +COPY testrp /testrp/ + +RUN . /testprovider_env/bin/activate && pip install -r /testprovider/requirements.txt +RUN . /testrp_env/bin/activate && pip install -r /testrp/requirements.txt + +# Install python and python dependencies +RUN apt-get update && \ + apt-get install -y wait-for-it + +# Install firefox +RUN apt-get install -y --no-install-recommends firefox-esr && \ + wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" -O /tmp/firefox.tar.bz2 && \ + tar xvf /tmp/firefox.tar.bz2 -C /opt && \ + rm /usr/bin/firefox && \ + ln -s /opt/firefox/firefox /usr/bin/firefox + +# Install geckodriver +RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux32.tar.gz" -O /tmp/geckodriver.tar.gz && \ + tar xvf /tmp/geckodriver.tar.gz -C /opt && \ + ln -s /opt/geckodriver /usr/bin/geckodriver diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testprovider b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testprovider new file mode 100644 index 0000000000000000000000000000000000000000..a5801f677a6514cdd36ceae84dc51b63dab84683 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testprovider @@ -0,0 +1,7 @@ +FROM python:3.6 +EXPOSE 8080 +COPY testprovider /code/ +WORKDIR /code + +RUN pip install -r requirements.txt +CMD ./bin/run.sh diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py2 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py2 new file mode 100644 index 0000000000000000000000000000000000000000..369b2742d7ca7d06b7fa6e2aa20bc71aad6e089d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py2 @@ -0,0 +1,7 @@ +FROM python:2-stretch +EXPOSE 8081 +COPY testrp /code/ +WORKDIR /code + +RUN pip install -r requirements.txt +CMD ./bin/run.sh diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py3 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py3 new file mode 100644 index 0000000000000000000000000000000000000000..f4d52e4dfe609d8e4fc6168da02bb518200d6f2d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py3 @@ -0,0 +1,7 @@ +FROM python:3-stretch +EXPOSE 8081 +COPY testrp /code/ +WORKDIR /code + +RUN pip install -r requirements.txt +CMD ./bin/run.sh diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrunner b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrunner new file mode 100644 index 0000000000000000000000000000000000000000..422da4a032271f4268b88120df86fbd18e2f13f8 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrunner @@ -0,0 +1,18 @@ +FROM python:3-stretch + +# Install python and python dependencies +RUN apt-get update && \ + apt-get install -y wait-for-it && \ + pip install six splinter + +# Install firefox +RUN apt-get install -y --no-install-recommends firefox-esr && \ + wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" -O /tmp/firefox.tar.bz2 && \ + tar xvf /tmp/firefox.tar.bz2 -C /opt && \ + rm /usr/bin/firefox && \ + ln -s /opt/firefox/firefox /usr/bin/firefox + +# Install geckodriver +RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux32.tar.gz" -O /tmp/geckodriver.tar.gz && \ + tar xvf /tmp/geckodriver.tar.gz -C /opt && \ + ln -s /opt/geckodriver /usr/bin/geckodriver diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/bin/run.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/bin/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..57f2c0d44126c394d2e1e8cff0e9bd208085068d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/bin/run.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +python manage.py migrate --noinput +python manage.py loaddata fixtures.json +python ./manage.py createuser paulus pauluspass paulus@localhost +python manage.py runserver 0.0.0.0:8080 + diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/fixtures.json b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/fixtures.json new file mode 100644 index 0000000000000000000000000000000000000000..19e122b27225b28db84a2d7f3b6b68bb34294a5d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/fixtures.json @@ -0,0 +1,115 @@ +[ + { + "model": "sites.site", + "pk": 1, + "fields": { + "domain": "testprovider:8080", + "name": "testprovider" + } + }, + { + "model": "oidc_provider.responsetype", + "pk": 1, + "fields": { + "value": "code", + "description": "code (Authorization Code Flow)" + } + }, + { + "model": "oidc_provider.responsetype", + "pk": 2, + "fields": { + "value": "id_token", + "description": "id_token (Implicit Flow)" + } + }, + { + "model": "oidc_provider.responsetype", + "pk": 3, + "fields": { + "value": "id_token token", + "description": "id_token token (Implicit Flow)" + } + }, + { + "model": "oidc_provider.responsetype", + "pk": 4, + "fields": { + "value": "code token", + "description": "code token (Hybrid Flow)" + } + }, + { + "model": "oidc_provider.responsetype", + "pk": 5, + "fields": { + "value": "code id_token", + "description": "code id_token (Hybrid Flow)" + } + }, + { + "model": "oidc_provider.responsetype", + "pk": 6, + "fields": { + "value": "code id_token token", + "description": "code id_token token (Hybrid Flow)" + } + }, + { + "model": "oidc_provider.client", + "pk": 1, + "fields": { + "name": "testrpHS256", + "owner": null, + "client_type": "confidential", + "client_id": "1", + "client_secret": "secret", + "jwt_alg": "HS256", + "date_created": "2017-11-10", + "website_url": "", + "terms_url": "", + "contact_email": "", + "logo": "", + "reuse_consent": true, + "require_consent": true, + "_redirect_uris": "http://localhost:8000/oidc/callback/", + "_post_logout_redirect_uris": "", + "_scope": "", + "response_types": [ + 1 + ] + } + }, + { + "model": "oidc_provider.client", + "pk": 2, + "fields": { + "name": "testrpRS256", + "owner": null, + "client_type": "confidential", + "client_id": "2", + "client_secret": "secret", + "jwt_alg": "RS256", + "date_created": "2017-11-10", + "website_url": "", + "terms_url": "", + "contact_email": "", + "logo": "", + "reuse_consent": true, + "require_consent": true, + "_redirect_uris": "http://localhost:8000/oidc/callback/", + "_post_logout_redirect_uris": "", + "_scope": "", + "response_types": [ + 1 + ] + } + }, + { + "model": "oidc_provider.rsakey", + "pk": 3, + "fields": { + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDAAgiIdiJG7GSMKTRbnGjWpHp1ulJ43/iQjDywWh5MP3in2PK8\nPVI6ItxIFLV81nWZMymA7hjfP7adOlxKY6rI+fExn8cTimI3W/oX6mHrPXm52uj/\nwe839pxxkeD7cmWgaif9Sujuy5AHUuUM1BTlO55POHkmhWyYMKC2P29qgQIDAQAB\nAoGAUHdJri6b1M8yoA6Qk6frw7AwZfAMqf1qxOEQefN6aQfcf7MKntqwAA8l88tB\n96xEokxvo0mlAMJJvIB9tusn4dIHKpmQGacQWVd/KONxPkvyuGgQXX5KCusZTbg7\ni6YQM52RGbExVFWLdGYJRBvzyfRkWX0b4LiderPZUiD6J/UCQQDZIgnLqYyGw3Ro\nnNboWYyOtLhKMF59f/0aSMXLlWdsnFG8kVm/7tw6jcDBalELci/+ExL2JACGwDea\n8DpvWiEDAkEA4mCovWmMDiS8tQCeY5NDic1wMp51+Ya8RX47bvb5F+X7SSE9L87y\n6eU9zVBSY8F+9npkvrxoU9PlKbS3Lzz1KwJAZ5/8BsuS+lnbe3Wmhtr93rlW3mk5\nHzHu7BVg+GkEI+xygcjoiVYImpU+MdB4fzrutpYJzZie+7BOmU4exTfBWwJBAKj+\nN3mO/Xrhee41VAhJuzV4I7XmDXQFXS8TmRKxVCq/COQC6EZ0W2q4M3a964OEw18E\n54hr5gYOPRjxS378JpkCQDjKw2Vyw0S0M8O2hOGuNsUtlGApYKt2iA41jGUf7bvO\nWz/tQuEIXQMd4e9zxNxOzPJOtjR1gyPZyi/FvsgDJDU=\n-----END RSA PRIVATE KEY-----" + } + } +] diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/manage.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/manage.py new file mode 100755 index 0000000000000000000000000000000000000000..5fccf17e2ac7382f4d86d3e79a1d789f72bda004 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oidcprovider.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/SAS/TMSS/test/oidc/example/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/__init__.py similarity index 100% rename from SAS/TMSS/test/oidc/example/__init__.py rename to SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/__init__.py diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/createuser.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/createuser.py new file mode 100644 index 0000000000000000000000000000000000000000..11fc496c521cf65641dbe5bc4e10509e379f231c --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/createuser.py @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Create a user in the OIDC provider. +""" + +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Create a user in the OIDC provider." + + def add_arguments(self, parser): + parser.add_argument("username", help="account username") + parser.add_argument("password", help="account password") + parser.add_argument("email", help="account email address") + + def handle(self, **options): + username = options["username"] + password = options["password"] + email = options["email"] + + if User.objects.filter(username=username).exists(): + self.stdout.write("User {} already exists.".format(username)) + return + + user = User.objects.create(username=username, email=email) + user.set_password(password) + user.save() + self.stdout.write("User {} created.".format(username)) diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/settings.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2eca62c36f16daada5279a294470405c38cfdb --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/settings.py @@ -0,0 +1,149 @@ +""" +Django settings for oidcprovider project. + +Generated by 'django-admin startproject' using Django 1.11.6. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '_brj&4ea0ec%ybc(rz32jpqwypdy4@d9lttg&g7!^e(m!-52si' +SESSION_COOKIE_NAME = 'oidcprovider' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] +SITE_ID = 1 + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.sites', + + 'oidc_provider', + 'oidcprovider', + 'pinax_theme_bootstrap', + 'account', + 'bootstrapform', + + +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + # django-user-accounts + 'account.middleware.LocaleMiddleware', + 'account.middleware.TimezoneMiddleware' +] + +ROOT_URLCONF = 'oidcprovider.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'pinax_theme_bootstrap.context_processors.theme', + 'account.context_processors.account', + ], + }, + }, +] + +WSGI_APPLICATION = 'oidcprovider.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' + + +# OIDC provider settings +LOGIN_URL = '/account/login' +ACCOUNT_EMAIL_CONFIRMATION_EMAIL = False + + +# Workaround to actually delete the account instead of marking it as inactive +def _delete_user(obj): + obj.user.delete() + + +ACCOUNT_DELETION_MARK_CALLBACK = _delete_user diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/home.html b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/home.html new file mode 100644 index 0000000000000000000000000000000000000000..6daa91684373aadf91bc4e4c9fcbb835e32260be --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/home.html @@ -0,0 +1,14 @@ +{% extends "site_base.html" %} + +{% block head_title %}Home{% endblock %} + +{% block body %} +<h1>Welcome to {% if SITE_NAME %}{{ SITE_NAME }}{% else %}testprovider{% endif %}!</h1> +<div> + {% if request.user.is_authenticated %} + <p>Current user: {{ user.email }}</p> + {% else %} + <p>User not logged in</p> + {% endif %} +</div> +{% endblock body %} diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/site_base.html b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/site_base.html new file mode 100644 index 0000000000000000000000000000000000000000..909a80cc317bb3043c437609636d45f44a184112 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/site_base.html @@ -0,0 +1,28 @@ +{% extends "theme_bootstrap/base.html" %} +{% load static %} + +{% block footer %} + <p>Test OIDC provider</p> +{% endblock %} + +{% block styles %} + <link rel="stylesheet" href="{% static 'pinax/css/theme.css' %}"> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" + integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" + crossorigin="anonymous"> + <link rel="stylesheet" + href="https://stackpath.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" + integrity="sha384-MI32KR77SgI9QAPUs+6R7leEOwtop70UsjEtFEezfKnMjXWx15NENsZpfDgq8m8S" + crossorigin="anonymous"> +{% endblock styles %} + +{% block scripts %} + <script src="https://code.jquery.com/jquery-2.2.4.min.js" + integrity="sha384-rY/jv8mMhqDabXSo+UCggqKtdmBfd3qC2/KvyTDNQ6PcUJXaxK1tMepoQda4g5vB" + crossorigin="anonymous"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" + integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" + crossorigin="anonymous"></script> +{% endblock scripts %} + diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/urls.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..3f8296b1167063f2b99f397ea3d166361a41e2a2 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/urls.py @@ -0,0 +1,12 @@ +from django.conf.urls import include, url +from django.contrib import admin + +from .views import HomePageView + +urlpatterns = [ + url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')), + url(r'^account/', include('account.urls')), + url(r'^admin/', admin.site.urls), + url(r'^$', HomePageView.as_view(), name='home'), + +] diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/views.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/views.py new file mode 100644 index 0000000000000000000000000000000000000000..14cc50fec47aa54e48618de782f6e235eab07fda --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/views.py @@ -0,0 +1,6 @@ +from django.views.generic.base import TemplateView + + +class HomePageView(TemplateView): + + template_name = "home.html" diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/wsgi.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/wsgi.py new file mode 100644 index 0000000000000000000000000000000000000000..a995efc052615dc3c1882ffff6318636e1f3711d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for oidcprovider project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oidcprovider.settings") + +application = get_wsgi_application() diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/requirements.txt b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..78415f8c8b022fbacb3f6dbe4678483093b76c1d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/requirements.txt @@ -0,0 +1,4 @@ +django<3 +django-oidc-provider +django-user-accounts +pinax-theme-bootstrap diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..6c365a5d40df50b7d86136df18a16d4cb67d3358 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +TEST_OIDC_ALGO=${TEST_OIDC_ALGO} +RUNNER="./bin/run_$TEST_OIDC_ALGO.sh" + +exec $RUNNER diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_hs.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_hs.sh new file mode 100755 index 0000000000000000000000000000000000000000..d2880cfa48ecabdfa4ad011e7d972eb71b365d37 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_hs.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export OIDC_RP_CLIENT_ID='1' +export OIDC_RP_CLIENT_SECRET='bd01adf93cfb' +python manage.py migrate --noinput +python manage.py runserver 0.0.0.0:8081 diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_rs.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_rs.sh new file mode 100755 index 0000000000000000000000000000000000000000..82e27b82659ce09f09899f653a8f8c51735ce816 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_rs.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +export OIDC_RP_IDP_SIGN_KEY=$(cat provider_rsa.key) +export OIDC_RP_CLIENT_ID='2' +export OIDC_RP_CLIENT_SECRET='a6b4dad2f215' +export OIDC_RP_SIGN_ALGO='RS256' +python manage.py migrate --noinput +python manage.py runserver 0.0.0.0:8081 diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/manage.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/manage.py new file mode 100755 index 0000000000000000000000000000000000000000..b588f3da043208c06dc027303e704d9a2f35b0f5 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrp.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/provider_rsa.key b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/provider_rsa.key new file mode 100644 index 0000000000000000000000000000000000000000..8d4b4180509d3405354da78ac948c4eda504dc3d --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/provider_rsa.key @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAAgiIdiJG7GSMKTRbnGjWpHp1 +ulJ43/iQjDywWh5MP3in2PK8PVI6ItxIFLV81nWZMymA7hjfP7adOlxKY6rI+fEx +n8cTimI3W/oX6mHrPXm52uj/we839pxxkeD7cmWgaif9Sujuy5AHUuUM1BTlO55P +OHkmhWyYMKC2P29qgQIDAQAB +-----END PUBLIC KEY----- diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/requirements.txt b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..4bb815165fb59dae48ce4d0cf1aff7e6ee6c610b --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/requirements.txt @@ -0,0 +1,6 @@ +django +djangorestframework +python-decouple +mozilla-django-oidc +six +splinter diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/settings.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..e746e99f9d076b29e5a1724eb0317b1b87f8e113 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/settings.py @@ -0,0 +1,153 @@ +""" +Django settings for testrp project. + +Generated by 'django-admin startproject' using Django 1.11.6. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +from decouple import config + + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '@n!q#(pw!ta3_^*!i7&8m(ev91qdju(5^ijx)a%5+upwv+s_u-' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'rest_framework', + 'mozilla_django_oidc', + 'testrp' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +# Django<1.10 Compatibility +MIDDLEWARE_CLASSES = MIDDLEWARE + +ROOT_URLCONF = 'testrp.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'testrp.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'mozilla_django_oidc.contrib.drf.OIDCAuthentication' + ] +} + + +# OIDC SETUP +AUTHENTICATION_BACKENDS = ( + 'mozilla_django_oidc.auth.OIDCAuthenticationBackend', +) + +OIDC_RP_CLIENT_ID = config('OIDC_RP_CLIENT_ID') +OIDC_RP_CLIENT_SECRET = config('OIDC_RP_CLIENT_SECRET') +OIDC_RP_SIGN_ALGO = config('OIDC_RP_SIGN_ALGO', default='HS256') +OIDC_RP_IDP_SIGN_KEY = config('OIDC_RP_IDP_SIGN_KEY', default=None) +OIDC_OP_AUTHORIZATION_ENDPOINT = 'http://testprovider:8080/openid/authorize' +OIDC_OP_TOKEN_ENDPOINT = 'http://testprovider:8080/openid/token' +OIDC_OP_USER_ENDPOINT = 'http://testprovider:8080/openid/userinfo' + +LOGOUT_REDIRECT_URL = '/' +LOGIN_REDIRECT_URL = '/' diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/templates/home.html b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/templates/home.html new file mode 100644 index 0000000000000000000000000000000000000000..0b07cc8c9ec7d6e92d3bed40928d6d5111d46074 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/templates/home.html @@ -0,0 +1,22 @@ +<html> + <body> + <div> + Welcome to testrp! + </div> + <div> + {% if request.user.is_authenticated %} + <p>Current user: {{ user.email }}</p> + <div> + <form action="/oidc/logout/" method="POST"> + {% csrf_token %} + <input type="submit" value="Logout"/> + </form> + </div> + {% else %} + <a href="{% url 'oidc_authentication_init' %}"> + Login + </a> + {% endif %} + </div> + </body> +</html> diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/urls.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..beaf5a5920817232dc4e9e3b36fb25c9b48c0c39 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import include, url + +from .views import HomePageView, TestAPIView + + +urlpatterns = [ + url(r'^oidc/', include('mozilla_django_oidc.urls')), + url(r'^api/$', TestAPIView.as_view(), name='api'), + url(r'^$', HomePageView.as_view(), name='home') +] diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/views.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/views.py new file mode 100644 index 0000000000000000000000000000000000000000..47b860cce32ef0b7be8f307f0ea352cb16ce486c --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/views.py @@ -0,0 +1,17 @@ +from django.views.generic.base import TemplateView +from mozilla_django_oidc.utils import is_authenticated +from rest_framework.response import Response +from rest_framework.views import APIView + + +class HomePageView(TemplateView): + + template_name = "home.html" + + +class TestAPIView(APIView): + + def get(self, request): + return Response({ + 'is_authenticated': is_authenticated(request.user) + }) diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/wsgi.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/wsgi.py new file mode 100644 index 0000000000000000000000000000000000000000..ea4648b82c599721ede45f39c98263c3fac19d73 --- /dev/null +++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for testrp project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrp.settings") + +application = get_wsgi_application() diff --git a/SAS/TMSS/test/oidc/create-keycloak-user.sh b/SAS/TMSS/test/oidc/keycloak/create-keycloak-user.sh similarity index 100% rename from SAS/TMSS/test/oidc/create-keycloak-user.sh rename to SAS/TMSS/test/oidc/keycloak/create-keycloak-user.sh diff --git a/SAS/TMSS/test/oidc/realm-export.json b/SAS/TMSS/test/oidc/keycloak/realm-export.json similarity index 100% rename from SAS/TMSS/test/oidc/realm-export.json rename to SAS/TMSS/test/oidc/keycloak/realm-export.json diff --git a/SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile b/SAS/TMSS/test/oidc/keycloak/tmss_keycloak_Dockerfile similarity index 100% rename from SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile rename to SAS/TMSS/test/oidc/keycloak/tmss_keycloak_Dockerfile diff --git a/SAS/TMSS/test/oidc/example/README.md b/SAS/TMSS/test/oidc/pyop_example/README.md similarity index 100% rename from SAS/TMSS/test/oidc/example/README.md rename to SAS/TMSS/test/oidc/pyop_example/README.md diff --git a/SAS/TMSS/test/oidc/pyop_example/__init__.py b/SAS/TMSS/test/oidc/pyop_example/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SAS/TMSS/test/oidc/example/app.py b/SAS/TMSS/test/oidc/pyop_example/app.py similarity index 100% rename from SAS/TMSS/test/oidc/example/app.py rename to SAS/TMSS/test/oidc/pyop_example/app.py diff --git a/SAS/TMSS/test/oidc/example/application.cfg b/SAS/TMSS/test/oidc/pyop_example/application.cfg similarity index 100% rename from SAS/TMSS/test/oidc/example/application.cfg rename to SAS/TMSS/test/oidc/pyop_example/application.cfg diff --git a/SAS/TMSS/test/oidc/example/https.crt b/SAS/TMSS/test/oidc/pyop_example/https.crt similarity index 100% rename from SAS/TMSS/test/oidc/example/https.crt rename to SAS/TMSS/test/oidc/pyop_example/https.crt diff --git a/SAS/TMSS/test/oidc/example/https.key b/SAS/TMSS/test/oidc/pyop_example/https.key similarity index 100% rename from SAS/TMSS/test/oidc/example/https.key rename to SAS/TMSS/test/oidc/pyop_example/https.key diff --git a/SAS/TMSS/test/oidc/example/requirements.txt b/SAS/TMSS/test/oidc/pyop_example/requirements.txt similarity index 100% rename from SAS/TMSS/test/oidc/example/requirements.txt rename to SAS/TMSS/test/oidc/pyop_example/requirements.txt diff --git a/SAS/TMSS/test/oidc/example/signing_key.pem b/SAS/TMSS/test/oidc/pyop_example/signing_key.pem similarity index 100% rename from SAS/TMSS/test/oidc/example/signing_key.pem rename to SAS/TMSS/test/oidc/pyop_example/signing_key.pem diff --git a/SAS/TMSS/test/oidc/example/templates/logout.jinja2 b/SAS/TMSS/test/oidc/pyop_example/templates/logout.jinja2 similarity index 100% rename from SAS/TMSS/test/oidc/example/templates/logout.jinja2 rename to SAS/TMSS/test/oidc/pyop_example/templates/logout.jinja2 diff --git a/SAS/TMSS/test/oidc/example/views.py b/SAS/TMSS/test/oidc/pyop_example/views.py similarity index 100% rename from SAS/TMSS/test/oidc/example/views.py rename to SAS/TMSS/test/oidc/pyop_example/views.py diff --git a/SAS/TMSS/test/oidc/example/wsgi.py b/SAS/TMSS/test/oidc/pyop_example/wsgi.py similarity index 100% rename from SAS/TMSS/test/oidc/example/wsgi.py rename to SAS/TMSS/test/oidc/pyop_example/wsgi.py diff --git a/SAS/TMSS/test/oidc/readme.txt b/SAS/TMSS/test/oidc/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..b50a9f02e515edfa650a9806a4fb0e256a8c296b --- /dev/null +++ b/SAS/TMSS/test/oidc/readme.txt @@ -0,0 +1,31 @@ +This directory contains a few different OpenID Connect Identity Provider implementations to test against. +This has not yet been tested/integrated with the web frontend POC behind nginx, but at least the Mozilla test container +works against the Django dev server. + +- Test Mozilla Django OIDC: This is currently the only working solution. It is a docker setup that the developers of + the Django plugin that we use have created for their own testing. The docker compose file + currently pulls things externally, so this is the vanilla setup. The downside of this is + that due to how OpenID works, the identity provider needs to know the callback URIs of the + services that use it and that is hardcoded. So either we set up our test deployment to use + same container/host names, or we have to patch things the way we need it. I chose the latter + and adapted the fixtures to have test client on localhost and also add a test user. + So building and running the oidc_tesprovider should provide an OpenID Identity Provider on + port 8080, which is comfortable to talk to Django on http://localhost:8000. + You should be able to logon on http://localhost:8000/oidc/authenticate as 'paulus'/'pauluspass' +- PYOP: pyop is a Python-based identity provider that can be simply installed via pip. It seems to be more of a library + than a mature and ready-to-deploy product. The project's repository contains an example implementation, which + looked good at first, since it's quite a simple flask-based setup without much overhead or tons of dependencies. + I almost got it to work, and there is useful logging once you managed to make Django actually talk to it, but + I did not continue on getting this to work. See below for some guidance: + A caveat of pyop is that there don't seem to be fixtures for preconfiguring clients, so before being able to + talk to it, you have to register as a client with the callback URL you want to use or it will complain, e.g.: + curl -k -X POST -d '{"redirect_uris": ["http://localhost:8000/oidc/callback/"] }' https://localhost:9090/registration + That will return a json with client_id and client_secret that have to be placed in the settings.py of Django, + or even better in environment variables before tmss is started (we could/should automize that for testing). + Pyop then accepts the request but chokes on it because it does not support the client authentication method + that Django uses for its request. (Must be client_secret_basic, but apparently is not.) This could also be an + issue of the Mozilla Django Plugin we use or configurable...? +- Keycloak: This seems to be a full blown and quite mature identity provider implementation in Java. + I found a nice docker solution that supposedly sets up a test provider with fixtures and all, + but unfortunately it does not build currently (Docker image seems unmaintained, so maybe not + the best choice anyway).