Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #23243: Hash API tokens - plugin #585

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 47 additions & 20 deletions api-authorizations/src/main/elm/sources/UserApiToken.elm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type alias Model =
{ contextPath : String
, token : Maybe Token
, error : Maybe (Html Msg)
, isNewToken : Bool
}


Expand Down Expand Up @@ -95,7 +96,7 @@ init : { contextPath : String } -> ( Model, Cmd Msg )
init flags =
let
initModel =
Model flags.contextPath Nothing Nothing
Model flags.contextPath Nothing Nothing False
in
( initModel
, getUserToken initModel
Expand Down Expand Up @@ -173,7 +174,7 @@ update msg model =
updateFromApiResult res model

CreateUserToken res ->
updateFromApiResult res model
updateFromApiResult res { model | isNewToken = True}

DeleteUserToken res ->
updateFromDelete res model
Expand Down Expand Up @@ -244,8 +245,8 @@ deleteUserToken model =
-- view when the token exists


tokenPresent : Token -> List (Html Msg)
tokenPresent token =
tokenPresent : Token -> Bool -> List (Html Msg)
tokenPresent token isNewToken =
let
( statusIcon, statusTxt, statusClass ) =
case token.enabled of
Expand All @@ -254,36 +255,62 @@ tokenPresent token =

False ->
( "fa-ban", "disabled", "text-info" )
hasClearTextToken = not (String.isEmpty token.token)
in
[ li []
[ if hasClearTextToken then
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-key" ] []
, text "Your personal token: "
, div [ class "help-block" ] [ b [] [ text token.token ] ]
]
]
else
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-key" ] []
, text "You have a personal token."
]
]
, if isNewToken then
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-exclamation-triangle" ] []
, text "Copy it now as it will not be re-displayed"
]
]
else
if hasClearTextToken then
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-key" ] []
, text "Your personal token: "
, div [ class "help-block" ] [ b [] [ text token.token ] ]
]
[ span [ class "fa fa-exclamation-triangle" ] []
, text "Deprecated token format, please re-create"
]
]
else
text ""
, li []
[ a [ class "no-click" ]
[ span [ class "fa fa-calendar" ] []
, text "Generated on "
, b [] [ text token.generationDate ]
]
[ span [ class "fa fa-calendar" ] []
, text "Generated on "
, b [] [ text token.generationDate ]
]
]
, li []
[ a [ class "no-click" ]
[ span [ class ("fa " ++ statusIcon) ] []
, text "Status: "
, b [ class ("text-capitalize " ++ statusClass) ] [ text statusTxt ]
]
[ span [ class ("fa " ++ statusIcon) ] []
, text "Status: "
, b [ class ("text-capitalize " ++ statusClass) ] [ text statusTxt ]
]
]
, li [ class "footer" ] [ a [ class "deleteToken", onClick DeleteButton ] [ text "Delete API Token" ] ]
, li [ class "footer" ] [ a [ class "deleteToken", onClick DeleteButton ] [ text "Delete API token" ] ]
]


tokenAbsent : Model -> List (Html Msg)
tokenAbsent model =
[ li [] [ a [ class "no-click no-token" ] [ text "You don't have an API token yet." ] ]
, li [ class "footer" ] [ a [ class "createToken", onClick CreateButton ] [ text "Create an API Token" ] ]
, li [ class "footer" ] [ a [ class "createToken", onClick CreateButton ] [ text "Create an API token" ] ]
]


Expand All @@ -299,7 +326,7 @@ view model =
]
++ (case model.token of
Just token ->
tokenPresent token
tokenPresent token model.isNewToken

Nothing ->
tokenAbsent model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,14 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
readApi.getById(ApiAccountId(authzToken.actor.name)).toBox match {
case Full(Some(token)) =>
val accounts: JValue = ("accounts" -> JArray(List(token.toJson)))
case Full(Some(account)) =>
val filtered = account.copy(token = if (account.token.isHashed) {
// Don't send hashes
ApiToken("")
} else {
account.token
})
val accounts: JValue = ("accounts" -> JArray(List(filtered.toJson)))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case Full(None) =>
Expand All @@ -109,20 +115,31 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
val now = DateTime.now
val secret = ApiToken.generate_secret(tokenGenerator)
val hash = ApiToken.hash(secret)
val account = ApiAccount(
ApiAccountId(authzToken.actor.name),
ApiAccountKind.User,
ApiAccountName(authzToken.actor.name),
ApiToken(tokenGenerator.newToken(32)),
ApiToken(hash),
s"API token for user '${authzToken.actor.name}'",
true,
isEnabled = true,
now,
now
)

writeApi.save(account, ModificationId(uuidGen.newUuid), authzToken.actor).toBox match {
case Full(token) =>
val accounts: JValue = ("accounts" -> JArray(List(token.toJson)))
case Full(account) =>
val accounts: JValue = ("accounts" -> JArray(
List(
account
.copy(
// Send clear text secret
token = ApiToken(secret)
)
.toJson
)
))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case eb: EmptyBox =>
Expand All @@ -137,8 +154,8 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
writeApi.delete(ApiAccountId(authzToken.actor.name), ModificationId(uuidGen.newUuid), authzToken.actor).toBox match {
case Full(token) =>
val accounts: JValue = ("accounts" -> ("id" -> token.value))
case Full(account) =>
val accounts: JValue = ("accounts" -> ("id" -> account.value))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case eb: EmptyBox =>
Expand All @@ -153,8 +170,14 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
readApi.getById(ApiAccountId(authzToken.actor.name)).toBox match {
case Full(Some(token)) =>
val accounts: JValue = ("accounts" -> JArray(List(token.toJson)))
case Full(Some(account)) =>
val filtered = account.copy(token = if (account.token.isHashed) {
// Don't send hashes
ApiToken("")
} else {
account.token
})
val accounts: JValue = ("accounts" -> JArray(List(filtered.toJson)))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case Full(None) =>
Expand Down