Skip to content

Commit e1c692c

Browse files
Merge pull request #95 from delphi-hub/authEndpoint
Added authentication endpoint
2 parents 404ecbe + 8ab2e45 commit e1c692c

File tree

16 files changed

+599
-67
lines changed

16 files changed

+599
-67
lines changed

OpenAPISpecification.yaml

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,87 @@ tags:
2020
containers
2121
- name: Docker Operations
2222
description: Operations on instances that are running in a docker container
23+
- name: User Management
24+
description: Operations related to the user database of Delphi-Management
2325
schemes:
2426
- https
2527
- http
2628
paths:
29+
/users/authenticate:
30+
post:
31+
tags:
32+
- User Management
33+
summary: Authenticates a user and returns a valid JWT
34+
description: >-
35+
This endpoints validates the username and password that must
36+
be supplied in the Authorization header (using HTTP Basic Authentication).
37+
If valid, a JSON Web Token will be generated and returned, that may be used
38+
to authenticate the user for subsequent requests.
39+
operationId: authenticate
40+
parameters:
41+
- in: header
42+
name: Delphi-Authorization
43+
description: >-
44+
Valid JWT that autenticates the calling entity.
45+
type: string
46+
required: true
47+
- in: header
48+
name: Authorization
49+
description: >-
50+
HTTP Basic Authentication following the schema 'Basic <User:Password>
51+
where the concatination of username and password is Base64-Encoded.
52+
type: string
53+
required: true
54+
responses:
55+
'200':
56+
description: Supplied data is valid, a JWT is returned
57+
schema:
58+
type: string
59+
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
60+
'401':
61+
description: Unauthorized, invalid username / password supplied.
62+
/users/add:
63+
post:
64+
tags:
65+
- User Management
66+
summary: Adds a new users for the registry
67+
description: >-
68+
Adds a new user that is passed in the requests entity. The id of the user
69+
will be returned.
70+
operationId: addUser
71+
parameters:
72+
- in: body
73+
name: DelphiUser
74+
description: The user to add
75+
required: true
76+
schema:
77+
type: object
78+
required:
79+
- userName
80+
- secret
81+
- userType
82+
properties:
83+
userName:
84+
type: string
85+
example: MyUser
86+
secret:
87+
type: string
88+
example: 123Pass
89+
userType:
90+
type: string
91+
enum:
92+
- Admin
93+
- User
94+
- Component
95+
responses:
96+
'200':
97+
description: OK, user has been added, id is returned
98+
schema:
99+
type: integer
100+
format: int64
101+
example: 42
102+
'400':
103+
description: Bad request, name already exists
27104
/instances/register:
28105
post:
29106
tags:
@@ -374,12 +451,15 @@ paths:
374451
description: 'Bad request, your label exceeded the character limit'
375452
'404':
376453
description: 'Not found, the id you specified could not be found'
377-
/instances/{Id}/logs:
454+
'/instances/{Id}/logs':
378455
get:
379456
tags:
380457
- Basic Operations
381458
summary: Retrieve the logging output of the specified instance
382-
description: This command retrieves the docker container logging output for the specified instance, if the instance is in fact running inside a docker container.
459+
description: >-
460+
This command retrieves the docker container logging output for the
461+
specified instance, if the instance is in fact running inside a docker
462+
container.
383463
operationId: retreiveLogs
384464
parameters:
385465
- name: Id
@@ -395,22 +475,28 @@ paths:
395475
type: boolean
396476
responses:
397477
'200':
398-
description: Success, log string is being returned
478+
description: 'Success, log string is being returned'
399479
schema:
400480
type: string
401-
example: "I am logging output .."
481+
example: I am logging output ..
402482
'400':
403483
description: Selected instance not running inside docker container
404484
'404':
405485
description: Id not found on the server
406486
'500':
407487
description: Internal Server Error
408-
/instances/{Id}/attach:
488+
'/instances/{Id}/attach':
409489
get:
410490
tags:
411491
- Basic Operations
412492
summary: Stream logging output from instance
413-
description: 'This command streams the docker container logging output for the specified instance. NOTE: This is a websocket endpoint, so only valid websocket requests will be processed. Swagger does not provide sufficient support for websockets, so this documentation might be confusing as it defines a HTTP method, etc. The names of parameters and response-codes are valid though.'
493+
description: >-
494+
This command streams the docker container logging output for the
495+
specified instance. NOTE: This is a websocket endpoint, so only valid
496+
websocket requests will be processed. Swagger does not provide
497+
sufficient support for websockets, so this documentation might be
498+
confusing as it defines a HTTP method, etc. The names of parameters and
499+
response-codes are valid though.
414500
operationId: streamLogs
415501
parameters:
416502
- name: Id
@@ -426,7 +512,7 @@ paths:
426512
type: boolean
427513
responses:
428514
'200':
429-
description: Success, logs are being streamed via websocket connection.
515+
description: 'Success, logs are being streamed via websocket connection.'
430516
'400':
431517
description: Selected instance not running inside docker container
432518
'404':
@@ -747,13 +833,12 @@ paths:
747833
description: One of the ids was not found on the server
748834
'500':
749835
description: Internal server error
750-
/instances/{Id}/command:
836+
'/instances/{Id}/command':
751837
post:
752838
tags:
753839
- Docker Operations
754840
summary: Runs a command into a docker container
755-
description: >-
756-
This command runs a specified command inside a docker container.
841+
description: This command runs a specified command inside a docker container.
757842
operationId: command
758843
parameters:
759844
- in: path
@@ -773,23 +858,21 @@ paths:
773858
properties:
774859
Command:
775860
type: string
776-
example: "rm -rf *"
861+
example: rm -rf *
777862
Privileged:
778863
type: boolean
779864
User:
780865
type: string
781866
example: root
782867
responses:
783868
'200':
784-
description: 'OK'
869+
description: OK
785870
'400':
786-
description: >-
787-
Cannot run command, ID is no docker container.
871+
description: 'Cannot run command, ID is no docker container.'
788872
'404':
789-
description: Cannot run command, ID not found.
873+
description: 'Cannot run command, ID not found.'
790874
'500':
791-
description: Internal server error, unknown operation result DESCRIPTION
792-
875+
description: 'Internal server error, unknown operation result DESCRIPTION'
793876
definitions:
794877
InstanceLink:
795878
type: object

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,19 @@ Before you can start the application, you have to make sure your configuration f
6060
|```dockerOperationTimeout``` | ```Timeout``` | ```Timeout(20 seconds)``` | Default timeout for docker operations. If any of the async Docker operations (deploy, stop, pause, ..) takes longer than this, it will be aborted.|
6161
|```defaultDockerUri``` | ```String``` | ```http://localhost:9095``` | Default uri to connect to docker. It will be used if the environment variable ```DELPHI_DOCKER_HOST``` is not specified.|
6262
|```jwtSecretKey``` | ```String``` | ```changeme``` | Secret key to use for JWT signature (HS256). This setting can be overridden by specifying the ```JWT_SECRET``` environment variable.|
63-
|```useInMemoryDB``` | ```Boolean``` | ```true``` | If set to true, all instance data will be kept in memory instead of using a MySQL database.|
64-
|```databaseHost``` | ```String``` | ```"jdbc:mysql://localhost/"``` | Host that the MySQL database is reachable at (only necessary if *useInMemoryDB* is false).|
65-
|```databaseName``` | ```String``` | ```""``` | Name of the MySQL database to use (only necessary if *useInMemoryDB* is false).|
66-
|```databaseDriver``` | ```String``` | ```"com.mysql.jdbc.Driver"``` | Driver to use for the MySQL connection (only necessary if *useInMemoryDB* is false).|
67-
|```databaseUsername``` | ```String``` | ```""``` | Username to use for the MySQL connection (only necessary if *useInMemoryDB* is false).|
68-
|```databasePassword``` | ```String``` | ```""``` | Password to use for the MySQL connection (only necessary if *useInMemoryDB* is false).|
63+
|```useInMemoryInstanceDB``` | ```Boolean``` | ```true``` | If set to true, all instance data will be kept in memory instead of using a MySQL database.|
64+
|```instanceDatabaseHost``` | ```String``` | ```"jdbc:mysql://localhost/"``` | Host that the MySQL instance database is reachable at (only necessary if *useInMemoryInstanceDB* is false).|
65+
|```instanceDatabaseName``` | ```String``` | ```""``` | Name of the MySQL instance database to use (only necessary if *useInMemoryInstanceDB* is false).|
66+
|```instanceDatabaseDriver``` | ```String``` | ```"com.mysql.jdbc.Driver"``` | Driver to use for the MySQL connection (only necessary if *useInMemoryInstanceDB* is false).|
67+
|```instanceDatabaseUsername``` | ```String``` | ```""``` | Username to use for the MySQL instance DB connection (only necessary if *useInMemoryInstanceDB* is false).|
68+
|```instanceDatabasePassword``` | ```String``` | ```""``` | Password to use for the MySQL instance DB connection (only necessary if *useInMemoryInstanceDB* is false).|
69+
|```useInMemoryAuthDB``` | ```Boolean``` | ```true``` | If set to true, all user data will be kept in memory instead of using a MySQL database.|
70+
|```authDatabaseHost``` | ```String``` | ```"jdbc:mysql://localhost/"``` | Host that the MySQL users database is reachable at (only necessary if *useInMemoryAuthDB* is false).|
71+
|```authDatabaseName``` | ```String``` | ```""``` | Name of the MySQL user database to use (only necessary if *useInMemoryAuthDB* is false).|
72+
|```authDatabaseDriver``` | ```String``` | ```"com.mysql.jdbc.Driver"``` | Driver to use for the MySQL users DB connection (only necessary if *useInMemoryAuthDB* is false).|
73+
|```authDatabaseUsername``` | ```String``` | ```""``` | Username to use for the MySQL users DB connection (only necessary if *useInMemoryAuthDB* is false).|
74+
|```authDatabasePassword``` | ```String``` | ```""``` | Password to use for the MySQL users DB connection (only necessary if *useInMemoryAuthDB* is false).|
75+
|```authenticationValidFor``` | ```Int``` | ```30``` | Default duration that user tokens are valid for (in minutes).|
6976
|```maxTotalNoRequest``` | ```Int``` | ```2000``` | Maximum number of requests that are allowed to be executed during the current refresh period regardless of their origin.|
7077
|```maxIndividualIpReq``` | ```Int``` | ```200``` | Maximum number of requests that are allowed to be executed during the current refresh period for one specific origin ip.|
7178
|```ipLogRefreshRate``` | ```FiniteDuration``` | ```2.minutes``` | Duration of the log refresh period.|

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/Configuration.scala

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,23 @@ class Configuration( ) {
3737
val jwtSecretKey: String = sys.env.getOrElse("JWT_SECRET", "changeme")
3838

3939
//Database configurations
40-
val useInMemoryDB = true
41-
val databaseHost = "jdbc:mysql://localhost/"
42-
val databaseName = ""
43-
val databaseDriver = "com.mysql.jdbc.Driver"
44-
val databaseUsername = ""
45-
val databasePassword = ""
40+
val useInMemoryInstanceDB = true
41+
val instanceDatabaseHost = "jdbc:mysql://localhost/"
42+
val instanceDatabaseName = ""
43+
val instanceDatabaseDriver = "com.mysql.jdbc.Driver"
44+
val instanceDatabaseUsername = ""
45+
val instanceDatabasePassword = ""
46+
47+
//Auth database configuration
48+
val useInMemoryAuthDB = true
49+
val authDatabaseHost = "jdbc:mysql://localhost/"
50+
val authDatabaseName = ""
51+
val authDatabaseDriver = "com.mysql.jdbc.Driver"
52+
val authDatabaseUsername = ""
53+
val authDatabasePassword = ""
54+
55+
//Authentication valid for the time
56+
val authenticationValidFor = 30 //minutes
4657

4758
//Request Limiter
4859
val maxTotalNoRequest: Int = 2000

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/Registry.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import akka.actor.ActorSystem
44
import akka.stream.ActorMaterializer
55
import de.upb.cs.swt.delphi.instanceregistry.Docker._
66
import de.upb.cs.swt.delphi.instanceregistry.connection.Server
7-
import de.upb.cs.swt.delphi.instanceregistry.daos.{DatabaseInstanceDAO, DynamicInstanceDAO, InstanceDAO}
7+
import de.upb.cs.swt.delphi.instanceregistry.daos._
88

99
import scala.concurrent.ExecutionContext
1010
import scala.language.postfixOps
@@ -18,14 +18,22 @@ object Registry extends AppLogging {
1818
val configuration = new Configuration()
1919

2020
private val dao : InstanceDAO = {
21-
if (configuration.useInMemoryDB) {
21+
if (configuration.useInMemoryInstanceDB) {
2222
new DynamicInstanceDAO(configuration)
2323
} else {
2424
new DatabaseInstanceDAO(configuration)
2525
}
2626
}
2727

28-
private val requestHandler = new RequestHandler(configuration, dao, DockerConnection.fromEnvironment(configuration))
28+
private val authDao: AuthDAO = {
29+
if (configuration.useInMemoryAuthDB) {
30+
new DynamicAuthDAO(configuration)
31+
} else {
32+
new DatabaseAuthDAO(configuration)
33+
}
34+
}
35+
36+
private val requestHandler = new RequestHandler(configuration, authDao, dao, DockerConnection.fromEnvironment(configuration))
2937

3038
private val server: Server = new Server(requestHandler)
3139

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/RequestHandler.scala

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import akka.stream.{ActorMaterializer, Materializer, OverflowStrategy}
99
import akka.util.Timeout
1010
import de.upb.cs.swt.delphi.instanceregistry.Docker.DockerActor._
1111
import de.upb.cs.swt.delphi.instanceregistry.Docker.{ContainerAlreadyStoppedException, DockerActor, DockerConnection}
12+
import de.upb.cs.swt.delphi.instanceregistry.authorization.AuthProvider
1213
import de.upb.cs.swt.delphi.instanceregistry.connection.RestClient
13-
import de.upb.cs.swt.delphi.instanceregistry.daos.InstanceDAO
14+
import de.upb.cs.swt.delphi.instanceregistry.daos.{AuthDAO, InstanceDAO}
1415
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.InstanceEnums.{ComponentType, InstanceState}
1516
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.LinkEnums.LinkState
1617
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model._
@@ -20,13 +21,15 @@ import scala.concurrent.{Await, ExecutionContext, Future}
2021
import scala.language.postfixOps
2122
import scala.util.{Failure, Success, Try}
2223

23-
class RequestHandler(configuration: Configuration, instanceDao: InstanceDAO, connection: DockerConnection) extends AppLogging {
24+
class RequestHandler(configuration: Configuration, authDao: AuthDAO, instanceDao: InstanceDAO, connection: DockerConnection) extends AppLogging {
2425

2526

2627
implicit val system: ActorSystem = Registry.system
2728
implicit val materializer: Materializer = ActorMaterializer()
2829
implicit val ec: ExecutionContext = system.dispatcher
2930

31+
val authProvider: AuthProvider = new AuthProvider(authDao)
32+
3033
val (eventActor, eventPublisher) = Source.actorRef[RegistryEvent](10, OverflowStrategy.dropNew)
3134
.toMat(Sink.asPublisher(fanout = true))(Keep.both)
3235
.run()
@@ -35,6 +38,7 @@ class RequestHandler(configuration: Configuration, instanceDao: InstanceDAO, con
3538
def initialize(): Unit = {
3639
log.info("Initializing request handler...")
3740
instanceDao.initialize()
41+
authDao.initialize()
3842
if (!instanceDao.allInstances().exists(instance => instance.name.equals("Default ElasticSearch Instance"))) {
3943
//Add default ES instance
4044
handleRegister(Instance(None,
@@ -54,6 +58,7 @@ class RequestHandler(configuration: Configuration, instanceDao: InstanceDAO, con
5458
def shutdown(): Unit = {
5559
eventActor ! PoisonPill
5660
instanceDao.shutdown()
61+
authDao.shutdown()
5762
}
5863

5964
/**
@@ -938,6 +943,24 @@ class RequestHandler(configuration: Configuration, instanceDao: InstanceDAO, con
938943
}
939944
}
940945

946+
/**
947+
* Add user to user database
948+
*
949+
* @param user
950+
* @return
951+
*/
952+
def handleAddUser(user: DelphiUser): Try[Long] = {
953+
954+
val noIdUser = DelphiUser(id = None, userName = user.userName, secret = user.secret, userType = user.userType)
955+
956+
authDao.addUser(noIdUser) match {
957+
case Success(id) =>
958+
log.info(s"Successfully handled create user request")
959+
Success(id)
960+
case Failure(x) => Failure(x)
961+
}
962+
}
963+
941964

942965
def isInstanceIdPresent(id: Long): Boolean = {
943966
instanceDao.hasInstance(id)

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/authorization/AccessToken.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ final case class AccessToken(userId: String,
99
issuedAt: DateTime,
1010
notBefore: DateTime)
1111

12+
1213
object AccessTokenEnums {
1314

1415
type UserType = UserType.Value

0 commit comments

Comments
 (0)