Skip to content

Commit e5721fa

Browse files
authored
Merge pull request #54 from delphi-hub/develop
Pre-Release Merge
2 parents 0a25c21 + 37a0005 commit e5721fa

38 files changed

+1336
-51
lines changed

.travis.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
language: scala
22
scala:
3-
- 2.12.4
3+
- 2.12.4
4+
script:
5+
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sbt ++$TRAVIS_SCALA_VERSION test; fi'
6+
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sbt ++$TRAVIS_SCALA_VERSION coverage test coverageReport coverageAggregate codacyCoverage; fi'
7+
after_success:
8+
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash <(curl -s https://codecov.io/bash); fi'

CONTRIBUTING.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Contribution Guide
2+
3+
We follow the GitHub [Fork & Pull][forkandpull] and [Git Flow][gitflow] workflow in this project.
4+
Please fork the official repository, develop in a branch based on the
5+
current develop branch, and submit a pull request (PR) after you are done.
6+
7+
[forkandpull]: https://help.github.com/articles/about-pull-requests/
8+
[gitflow]: http://nvie.com/posts/a-successful-git-branching-model/
9+
10+
## Checklist before submitting a Pull Request
11+
12+
Before you submit your PR, please go through this list and check if
13+
your request fulfills these points.
14+
15+
- Do you have more changes/additions/deletions in your PR than you expected?
16+
- Do you have tests for your changes?
17+
- Do all tests of the project succeed?
18+
- Is your pull request based on the `develop` branch? [(cf. GitFlow)][gitflow]
19+
20+
If you check these items before, your pull request is more likely to be
21+
included into the project quickly.
22+

LICENSE

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,7 @@
175175

176176
END OF TERMS AND CONDITIONS
177177

178-
APPENDIX: How to apply the Apache License to your work.
179-
180-
To apply the Apache License to your work, attach the following
181-
boilerplate notice, with the fields enclosed by brackets "[]"
182-
replaced with your own identifying information. (Don't include
183-
the brackets!) The text should be enclosed in the appropriate
184-
comment syntax for the file format. We also recommend that a
185-
file or class name and description of purpose be included on the
186-
same "printed page" as the copyright notice for easier
187-
identification within third-party archives.
188-
189-
Copyright [yyyy] [name of copyright owner]
178+
Copyright 2018 The Delphi Team (represented by Ben Hermann)
190179

191180
Licensed under the Apache License, Version 2.0 (the "License");
192181
you may not use this file except in compliance with the License.

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Delphi Management Console
2+
3+
A management console for the Delphi platform.
4+
5+
We are currently in pre-alpha state! There is no release and the code in
6+
this repository is purely experimental!
7+
8+
|branch | status | codacy |
9+
| :---: | :---: | :---: |
10+
| master | [![Build Status](https://travis-ci.org/delphi-hub/delphi-management.svg?branch=master)](https://travis-ci.org/delphi-hub/delphi-management) | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/0a50d2132e6b46adb6f6eb36b6ddc4e7)](https://www.codacy.com/app/delphi-hub/delphi-management?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=delphi-hub/delphi-management&amp;utm_campaign=Badge_Grade)|
11+
| develop | [![Build Status](https://travis-ci.org/delphi-hub/delphi-management.svg?branch=develop)](https://travis-ci.org/delphi-hub/delphi-management) | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/0a50d2132e6b46adb6f6eb36b6ddc4e7?branch=develop)](https://www.codacy.com/app/delphi-hub/delphi-management?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=delphi-hub/delphi-management&amp;utm_campaign=Badge_Grade) |
12+
13+
## What is the Delphi Management Console?
14+
15+
It is a web application to allow administrators to control the crawling process.
16+
17+
## How does it work?
18+
19+
It takes commands from authenticated administrators over the web interface and issues the appropriate steps in the crawler.
20+
21+
## How can I use it?
22+
23+
For any deployed instance you need an administrator account to interact with the application.
24+
You can start your own instance by executing
25+
```
26+
sbt run
27+
```
28+
29+
## Community
30+
31+
Feel welcome to join our chatroom on Gitter: [![Join the chat at https://gitter.im/delphi-hub/delphi](https://badges.gitter.im/delphi-hub/delphi.svg)](https://gitter.im/delphi-hub/delphi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
32+
33+
34+
## Contributing
35+
36+
Contributions are *very* welcome!
37+
38+
Before contributing, please read our [Code of Conduct](CODE_OF_CONDUCT.md).
39+
40+
Refer to the [Contribution Guide](CONTRIBUTING.md) for details about the workflow.
41+
We use Pull Requests to collect contributions. Especially look out for "help wanted" issues
42+
[![GitHub issues by-label](https://img.shields.io/github/issues/delphi-hub/delphi-management/help%20wanted.svg)](https://github.com/delphi-hub/delphi-management/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22),
43+
but feel free to work on other issues as well.
44+
You can ask for clarification in the issues directly, or use our Gitter
45+
chat for a more interactive experience.
46+
47+
[![GitHub issues](https://img.shields.io/github/issues/delphi-hub/delphi-management.svg)](https://github.com/delphi-hub/delphi-management/issues)
48+
49+
50+
## License
51+
52+
The Delphi CLI is open source and available under Apache 2 License.
53+
54+
[![GitHub license](https://img.shields.io/github/license/delphi-hub/delphi-management.svg)](https://github.com/delphi-hub/delphi-management/blob/master/LICENSE)

app/controllers/AuthController.scala

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package controllers
2+
3+
import javax.inject.Inject
4+
5+
import scala.concurrent.Future
6+
import scala.concurrent.duration.FiniteDuration
7+
import net.ceedubs.ficus.Ficus.{finiteDurationReader, toFicusConfig, optionValueReader}
8+
import com.mohiva.play.silhouette.api.Authenticator.Implicits.RichDateTime
9+
import com.mohiva.play.silhouette.api.{Environment, Silhouette}
10+
import com.mohiva.play.silhouette.api.exceptions.ProviderException
11+
import com.mohiva.play.silhouette.api.util.Credentials
12+
import com.mohiva.play.silhouette.impl.providers.CredentialsProvider
13+
import play.api.Configuration
14+
import play.api.data.Form
15+
import play.api.data.Forms.{boolean, email, mapping, nonEmptyText}
16+
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
17+
import play.api.i18n.{I18nSupport, Messages, MessagesApi}
18+
import scala.concurrent.ExecutionContext.Implicits.global
19+
import services.UserService
20+
import org.joda.time.DateTime
21+
import AuthForms.SignInForm
22+
import utils.auth.DefaultEnv
23+
24+
/**
25+
* Form used to store the three input variables when logging in: E-Mail, Password, RememberMe
26+
*/
27+
object AuthForms {
28+
29+
case class SignInData(email:String, password:String, rememberMe:Boolean)
30+
val SignInForm = Form(mapping(
31+
"email" -> email,
32+
"password" -> nonEmptyText,
33+
"rememberMe" -> boolean
34+
)(SignInData.apply)(SignInData.unapply)
35+
)
36+
37+
}
38+
39+
/**
40+
* Controller handling authentification related GET requests
41+
*
42+
* @param messagesApi Injected handle of current play MessagesApi
43+
* @param env Injected handle of current silhouette Environment
44+
* @param credentialsProvider Injected handle of the CredetialsProvider
45+
* @param userService Injected handle of the current UserService (defined in services/UserService.scala, bound in Module.scala)
46+
* @param configuration Injected handle of current application configuration
47+
* @param cc Injected handle of current ControllerComponents, needed to extend superclass AbstractController
48+
* @param silhouette Injected handle of current silhouette instance
49+
*/
50+
class AuthController @Inject() (
51+
override val messagesApi:MessagesApi,
52+
val env:Environment[DefaultEnv],
53+
credentialsProvider: CredentialsProvider,
54+
userService: UserService,
55+
configuration: Configuration,
56+
cc: ControllerComponents,
57+
silhouette: Silhouette[DefaultEnv]) extends AbstractController(cc) with I18nSupport{
58+
59+
60+
61+
/**
62+
* Called on GET /auth/signIn, will show the signIn page. If the user already is logged on, he will be redirected to
63+
* the index page
64+
*
65+
* @return Action redirecting the user
66+
*/
67+
def signIn: Action[AnyContent] = silhouette.UserAwareAction.async { implicit request =>
68+
Future.successful(request.identity match {
69+
case Some(_) => Redirect(routes.HomeController.index())
70+
case None => Ok(views.html.auth.signIn(SignInForm))
71+
})
72+
}
73+
74+
/**
75+
* Called when the user hits Enter / Login-Button on the login-page. Validates login data from the Form defined above
76+
* and redirects the user to the index page if supplied credentials are valid. Handles any error during login.
77+
*
78+
* @return Corrsponding action
79+
*/
80+
def authenticate : Action[AnyContent] = Action.async { implicit request =>
81+
SignInForm.bindFromRequest.fold(
82+
bogusForm => Future.successful(BadRequest(views.html.auth.signIn(bogusForm))),
83+
signInData => {
84+
val credentials = Credentials(signInData.email, signInData.password)
85+
credentialsProvider.authenticate(credentials).flatMap { loginInfo =>
86+
userService.retrieve(loginInfo).flatMap {
87+
case None =>
88+
Future.successful(Redirect(routes.AuthController.signIn()).flashing("error" -> Messages("error.noUser")))
89+
case Some(user) if !user.profileFor(loginInfo).exists(_.confirmed) =>
90+
Future.successful(Redirect(routes.AuthController.signIn()).flashing("error" -> Messages("error.unregistered", signInData.email)))
91+
case Some(_) => for {
92+
authenticator <- env.authenticatorService.create(loginInfo).map {
93+
case authenticator:Any if signInData.rememberMe =>
94+
val c = configuration.underlying
95+
authenticator.copy(
96+
expirationDateTime = new DateTime() + c.as[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorExpiry"),
97+
idleTimeout = c.getAs[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorIdleTimeout"),
98+
cookieMaxAge = c.getAs[FiniteDuration]("silhouette.authenticator.rememberMe.cookieMaxAge")
99+
)
100+
case authenticator:Any => authenticator
101+
}
102+
value <- env.authenticatorService.init(authenticator)
103+
result <- env.authenticatorService.embed(value, Redirect(routes.HomeController.index()))
104+
} yield result
105+
}
106+
}.recover {
107+
case _:ProviderException => Redirect(routes.AuthController.signIn()).flashing("error" -> Messages("error.invalidCredentials"))
108+
}
109+
}
110+
)
111+
}
112+
113+
/**
114+
* Called when signing out. Deletes the cookie that marked the user as logged in, and redirects him to the index page, which
115+
* will again redirect him to the signin page
116+
*
117+
* @return Action redirecting the user
118+
*/
119+
def signOut : Action[AnyContent] = silhouette.SecuredAction.async { implicit request =>
120+
env.authenticatorService.discard(request.authenticator, Redirect(routes.HomeController.index()))
121+
}
122+
123+
124+
125+
126+
127+
128+
}

app/controllers/HomeController.scala

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
package controllers
22

3-
import javax.inject._
4-
import play.api.mvc._
3+
import com.mohiva.play.silhouette.api.Silhouette
4+
import javax.inject.{Inject,Singleton}
5+
import play.api.i18n.{I18nSupport, MessagesApi}
6+
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
7+
import utils.auth.DefaultEnv
8+
import utils.ErrorHandler
59

610
/**
7-
* Created by benhermann on 02.01.18.
11+
* Controller handling GET requests for the index page
12+
*
13+
* @param messageApi Injected handle of current play MessagesApi
14+
* @param cc Injected handle of ControllerComponents, needed to extend superclass AbstractController
15+
* @param silhouette Injected handle of current Silhouette instance
816
*/
917
@Singleton
10-
class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
18+
class HomeController @Inject()(messageApi: MessagesApi,
19+
cc: ControllerComponents,
20+
silhouette: Silhouette[DefaultEnv]) extends AbstractController(cc) with I18nSupport{
1121

1222
/**
13-
* Create an Action to render an HTML page with a welcome message.
14-
* The configuration in the `routes` file means that this method
15-
* will be called when the application receives a `GET` request with
16-
* a path of `/`.
23+
* Create a SecuredAction to render the index page when receiving GET with path '/'. If the user is not logged in,
24+
* the passed instance of MyErrorHandler will redirect him to the login page.
1725
*/
18-
def index = Action {
19-
Ok(views.html.index("Delphi - Management Interface"))
20-
}
2126

27+
def index : Action[AnyContent] = silhouette.SecuredAction(new ErrorHandler(messageApi)) { implicit request => {
28+
29+
Ok(views.html.index(Option(request.identity), Option(request.authenticator.loginInfo), Option(JavaVersion(None)), Option(HostName(None)),Option(ScalaVersion(None)), Option(PlatformName(None))))
30+
}
31+
}
2232
}

app/controllers/SystemInfo.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package controllers
2+
3+
import java.net.InetAddress
4+
import play.core.PlayVersion
5+
6+
object JavaVersion {
7+
def apply(javaVersionPrefix: Option[String]): String = {
8+
val version = sys.props.get("java.version") getOrElse {sys.error("failed to get system property java.version")}
9+
10+
javaVersionPrefix match {
11+
case Some(prefix) =>
12+
if (!version.startsWith(prefix)) {
13+
sys.error(s"javac version ${version} may not be used to publish, it has to start with ${prefix} due to javaVersionPrefix setting")
14+
}
15+
case None =>
16+
}
17+
version
18+
}
19+
}
20+
21+
object HostName {
22+
def apply(host: Option[String]): String = {
23+
var hostname = InetAddress.getLocalHost().getHostName()
24+
hostname
25+
}
26+
}
27+
28+
object PlatformName {
29+
def apply(platform: Option[String]): String = {
30+
var os = "os.name";
31+
var version = "os.version";
32+
var osVersion = System.getProperty(os) + " " + System.getProperty(version)
33+
osVersion
34+
}
35+
}
36+
37+
object ScalaVersion {
38+
def apply(browser: Option[String]): String = {
39+
var scalaVersion = PlayVersion.current
40+
scalaVersion
41+
}
42+
}

app/daos/MockAdminUser.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package daos
2+
3+
import java.util.UUID
4+
5+
import com.mohiva.play.silhouette.api.LoginInfo
6+
import com.mohiva.play.silhouette.api.util.PasswordHasher
7+
import com.mohiva.play.silhouette.impl.providers.CredentialsProvider
8+
import javax.inject.Inject
9+
import models.{Profile, User}
10+
import play.api.Configuration
11+
12+
/**
13+
* Class providing access to a mocked administrator user object
14+
*/
15+
class MockAdminUser @Inject() (configuration: Configuration){
16+
17+
//Define constant values for fake user
18+
val adminMail : String = configuration.underlying.getString("mock.admin.email")
19+
val adminFullName = "Delphi Administrator"
20+
val adminFirstName = "Delphi"
21+
val adminLastName = "Administrator"
22+
val adminPassword :String = configuration.underlying.getString("mock.admin.password")
23+
24+
/**
25+
* Provide access to the User object
26+
* @param passwordHasher PasswordHasher to user for PasswordInfo
27+
* @return
28+
*/
29+
def getAdminUserMock(passwordHasher:PasswordHasher) : User = User(
30+
id = UUID.randomUUID(),
31+
profiles=List(Profile(loginInfo = LoginInfo(CredentialsProvider.ID,adminMail),
32+
confirmed = true, email = Option(adminMail),
33+
firstName = Option(adminFirstName),
34+
lastName = Option(adminLastName),
35+
fullName = Option(adminFullName),
36+
passwordInfo = Option(passwordHasher.hash(adminPassword) )
37+
))
38+
)
39+
40+
}

0 commit comments

Comments
 (0)