2019-01-16 15:01:38 +00:00
package auth
import (
2020-07-07 21:57:52 +00:00
"errors"
2019-02-20 00:53:25 +00:00
"log"
2019-05-24 06:04:58 +00:00
"net/http"
2021-06-10 22:09:04 +00:00
"time"
2019-01-18 08:13:33 +00:00
"github.com/asaskevich/govalidator"
2019-01-16 15:01:38 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
2021-02-23 03:21:39 +00:00
portainer "github.com/portainer/portainer/api"
2020-07-07 21:57:52 +00:00
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
2019-01-16 15:01:38 +00:00
)
2019-01-18 08:13:33 +00:00
type oauthPayload struct {
2021-02-23 03:21:39 +00:00
// OAuth code returned from OAuth Provided
2019-01-18 08:13:33 +00:00
Code string
}
func ( payload * oauthPayload ) Validate ( r * http . Request ) error {
if govalidator . IsNull ( payload . Code ) {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid OAuth authorization code" )
2019-01-18 08:13:33 +00:00
}
return nil
}
2021-06-10 22:09:04 +00:00
func ( handler * Handler ) authenticateOAuth ( code string , settings * portainer . OAuthSettings ) ( string , * time . Time , error ) {
2020-08-05 08:36:46 +00:00
if code == "" {
2021-06-10 22:09:04 +00:00
return "" , nil , errors . New ( "Invalid OAuth authorization code" )
2019-02-18 01:46:34 +00:00
}
2020-08-05 08:36:46 +00:00
if settings == nil {
2021-06-10 22:09:04 +00:00
return "" , nil , errors . New ( "Invalid OAuth configuration" )
2019-02-18 01:46:34 +00:00
}
2021-06-10 22:09:04 +00:00
username , expiryTime , err := handler . OAuthService . Authenticate ( code , settings )
2019-02-18 01:46:34 +00:00
if err != nil {
2021-06-10 22:09:04 +00:00
return "" , nil , err
2019-02-18 01:46:34 +00:00
}
2021-06-10 22:09:04 +00:00
return username , expiryTime , nil
2019-02-18 01:46:34 +00:00
}
2021-06-10 22:09:04 +00:00
// @id ValidateOAuth
// @summary Authenticate with OAuth
// @tags auth
// @accept json
// @produce json
// @param body body oauthPayload true "OAuth Credentials used for authentication"
// @success 200 {object} authenticateResponse "Success"
// @failure 400 "Invalid request"
// @failure 422 "Invalid Credentials"
// @failure 500 "Server error"
// @router /auth/oauth/validate [post]
2019-01-18 08:15:02 +00:00
func ( handler * Handler ) validateOAuth ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
2019-01-16 15:01:38 +00:00
var payload oauthPayload
err := request . DecodeAndValidateJSONPayload ( r , & payload )
if err != nil {
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Invalid request payload" , Err : err }
2019-01-16 15:01:38 +00:00
}
2020-05-20 05:23:15 +00:00
settings , err := handler . DataStore . Settings ( ) . Settings ( )
2019-01-16 15:01:38 +00:00
if err != nil {
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to retrieve settings from the database" , Err : err }
2019-01-16 15:01:38 +00:00
}
2021-06-10 22:09:04 +00:00
if settings . AuthenticationMethod != portainer . AuthenticationOAuth {
return & httperror . HandlerError { StatusCode : http . StatusForbidden , Message : "OAuth authentication is not enabled" , Err : errors . New ( "OAuth authentication is not enabled" ) }
2019-01-16 15:01:38 +00:00
}
2021-06-10 22:09:04 +00:00
username , expiryTime , err := handler . authenticateOAuth ( payload . Code , & settings . OAuthSettings )
2019-01-16 15:01:38 +00:00
if err != nil {
2019-02-20 00:53:25 +00:00
log . Printf ( "[DEBUG] - OAuth authentication error: %s" , err )
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to authenticate through OAuth" , Err : httperrors . ErrUnauthorized }
2019-01-16 15:01:38 +00:00
}
2020-05-20 05:23:15 +00:00
user , err := handler . DataStore . User ( ) . UserByUsername ( username )
2020-07-07 21:57:52 +00:00
if err != nil && err != bolterrors . ErrObjectNotFound {
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to retrieve a user with the specified username from the database" , Err : err }
2019-01-16 15:01:38 +00:00
}
2019-01-18 08:56:16 +00:00
if user == nil && ! settings . OAuthSettings . OAuthAutoCreateUsers {
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusForbidden , Message : "Account not created beforehand in Portainer and automatic user provisioning not enabled" , Err : httperrors . ErrUnauthorized }
2019-01-16 15:01:38 +00:00
}
2019-01-18 08:56:16 +00:00
if user == nil {
user = & portainer . User {
2020-08-11 05:41:37 +00:00
Username : username ,
Role : portainer . StandardUserRole ,
2019-01-16 15:01:38 +00:00
}
2020-05-20 05:23:15 +00:00
err = handler . DataStore . User ( ) . CreateUser ( user )
2019-01-16 15:01:38 +00:00
if err != nil {
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to persist user inside the database" , Err : err }
2019-01-16 15:01:38 +00:00
}
2019-02-17 06:01:42 +00:00
if settings . OAuthSettings . DefaultTeamID != 0 {
membership := & portainer . TeamMembership {
UserID : user . ID ,
TeamID : settings . OAuthSettings . DefaultTeamID ,
Role : portainer . TeamMember ,
}
2020-05-20 05:23:15 +00:00
err = handler . DataStore . TeamMembership ( ) . CreateTeamMembership ( membership )
2019-02-17 06:01:42 +00:00
if err != nil {
2021-06-10 22:09:04 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to persist team membership inside the database" , Err : err }
2019-02-17 06:01:42 +00:00
}
}
2019-12-04 02:32:55 +00:00
2019-01-16 15:01:38 +00:00
}
2021-06-10 22:09:04 +00:00
return handler . writeTokenForOAuth ( w , user , expiryTime )
2019-01-16 15:01:38 +00:00
}