2021-10-23 03:07:45 +08:00
package auth
import (
2022-05-27 21:21:14 +08:00
"context"
2021-10-23 03:07:45 +08:00
"encoding/json"
"fmt"
2022-01-07 04:05:38 +08:00
"io"
2021-10-23 03:07:45 +08:00
"net/http"
2021-12-07 04:31:08 +08:00
"github.com/gravitl/netmaker/logger"
2021-10-23 03:07:45 +08:00
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft"
)
var azure_ad_functions = map [ string ] interface { } {
init_provider : initAzureAD ,
get_user_info : getAzureUserInfo ,
handle_callback : handleAzureCallback ,
handle_login : handleAzureLogin ,
verify_user : verifyAzureUser ,
}
// == handle azure ad authentication here ==
func initAzureAD ( redirectURL string , clientID string , clientSecret string ) {
auth_provider = & oauth2 . Config {
RedirectURL : redirectURL ,
ClientID : clientID ,
ClientSecret : clientSecret ,
Scopes : [ ] string { "User.Read" } ,
2022-01-03 23:01:37 +08:00
Endpoint : microsoft . AzureADEndpoint ( servercfg . GetAzureTenant ( ) ) ,
2021-10-23 03:07:45 +08:00
}
}
func handleAzureLogin ( w http . ResponseWriter , r * http . Request ) {
2022-09-14 03:25:56 +08:00
var oauth_state_string = logic . RandomString ( user_signin_length )
2021-10-23 03:07:45 +08:00
if auth_provider == nil && servercfg . GetFrontendURL ( ) != "" {
2021-11-03 01:25:15 +08:00
http . Redirect ( w , r , servercfg . GetFrontendURL ( ) + "/login?oauth=callback-error" , http . StatusTemporaryRedirect )
2021-10-23 03:07:45 +08:00
return
} else if auth_provider == nil {
fmt . Fprintf ( w , "%s" , [ ] byte ( "no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials" ) )
return
}
2022-08-04 22:03:39 +08:00
if err := logic . SetState ( oauth_state_string ) ; err != nil {
http . Redirect ( w , r , servercfg . GetFrontendURL ( ) + "/login?oauth=callback-error" , http . StatusTemporaryRedirect )
return
}
2021-10-23 03:07:45 +08:00
var url = auth_provider . AuthCodeURL ( oauth_state_string )
http . Redirect ( w , r , url , http . StatusTemporaryRedirect )
}
func handleAzureCallback ( w http . ResponseWriter , r * http . Request ) {
2022-09-14 03:25:56 +08:00
var rState , rCode = getStateAndCode ( r )
var content , err = getAzureUserInfo ( rState , rCode )
2021-10-23 03:07:45 +08:00
if err != nil {
2021-12-07 04:31:08 +08:00
logger . Log ( 1 , "error when getting user info from azure:" , err . Error ( ) )
2021-11-03 01:25:15 +08:00
http . Redirect ( w , r , servercfg . GetFrontendURL ( ) + "/login?oauth=callback-error" , http . StatusTemporaryRedirect )
2021-10-23 03:07:45 +08:00
return
}
_ , err = logic . GetUser ( content . UserPrincipalName )
if err != nil { // user must not exists, so try to make one
if err = addUser ( content . UserPrincipalName ) ; err != nil {
return
}
}
var newPass , fetchErr = fetchPassValue ( "" )
if fetchErr != nil {
return
}
// send a netmaker jwt token
var authRequest = models . UserAuthParams {
UserName : content . UserPrincipalName ,
Password : newPass ,
}
var jwt , jwtErr = logic . VerifyAuthRequest ( authRequest )
if jwtErr != nil {
2021-12-07 04:31:08 +08:00
logger . Log ( 1 , "could not parse jwt for user" , authRequest . UserName )
2021-10-23 03:07:45 +08:00
return
}
2021-12-07 04:31:08 +08:00
logger . Log ( 1 , "completed azure OAuth sigin in for" , content . UserPrincipalName )
2021-11-03 01:25:15 +08:00
http . Redirect ( w , r , servercfg . GetFrontendURL ( ) + "/login?login=" + jwt + "&user=" + content . UserPrincipalName , http . StatusPermanentRedirect )
2021-10-23 03:07:45 +08:00
}
2022-09-14 03:25:56 +08:00
func getAzureUserInfo ( state string , code string ) ( * OAuthUser , error ) {
2022-08-04 22:03:39 +08:00
oauth_state_string , isValid := logic . IsStateValid ( state )
2022-09-14 03:25:56 +08:00
if ( ! isValid || state != oauth_state_string ) && ! isStateCached ( state ) {
2021-10-23 03:07:45 +08:00
return nil , fmt . Errorf ( "invalid oauth state" )
}
2022-05-27 21:21:14 +08:00
var token , err = auth_provider . Exchange ( context . Background ( ) , code )
2021-10-23 03:07:45 +08:00
if err != nil {
return nil , fmt . Errorf ( "code exchange failed: %s" , err . Error ( ) )
}
var data [ ] byte
data , err = json . Marshal ( token )
if err != nil {
return nil , fmt . Errorf ( "failed to convert token to json: %s" , err . Error ( ) )
}
var httpReq , reqErr = http . NewRequest ( "GET" , "https://graph.microsoft.com/v1.0/me" , nil )
if reqErr != nil {
return nil , fmt . Errorf ( "failed to create request to GitHub" )
}
httpReq . Header . Set ( "Authorization" , "Bearer " + token . AccessToken )
response , err := http . DefaultClient . Do ( httpReq )
if err != nil {
return nil , fmt . Errorf ( "failed getting user info: %s" , err . Error ( ) )
}
defer response . Body . Close ( )
2022-01-07 04:05:38 +08:00
contents , err := io . ReadAll ( response . Body )
2021-10-23 03:07:45 +08:00
if err != nil {
return nil , fmt . Errorf ( "failed reading response body: %s" , err . Error ( ) )
}
2022-09-14 03:25:56 +08:00
var userInfo = & OAuthUser { }
2021-10-23 03:07:45 +08:00
if err = json . Unmarshal ( contents , userInfo ) ; err != nil {
return nil , fmt . Errorf ( "failed parsing email from response data: %s" , err . Error ( ) )
}
userInfo . AccessToken = string ( data )
return userInfo , nil
}
func verifyAzureUser ( token * oauth2 . Token ) bool {
return token . Valid ( )
}