WSG 02.06.05.07 - Howto use Bentley STS / OAuth2 authentification ?

Hi,

I'm writing a program which extract informations from PW datasource, using the Web Services Gateway.

We use Bentley IMS to connect to datasource, so I want to use the same method. The documentation mention Bentley STS and OAuth2, but is not very precise and I don't find a way to achieve that :

All requests having a {RepositoryId} parameter must have one of the following:

  • Basic Authorization header. For example:
    Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
    The Basic parameter is a base-64 encoded string "username:password".
  • Token header with a token that is understood by the plugin. Currently supported token versions are Bentley STS and OAuth2.

Does anyone can explain how to configure the header using the token, or give an example ?

Thanks

Benjamin

Parents
  • I've just been doing some work in this area and it was pointed out that there has been no answer to this.Here's a very simple PowerShell script using WSG to download files. You will need at least version 1.11.2.0 of PWPS_DAB to run this script.

    $wsgURL = 'decide-pwce-us-ws.bentley.com/.../Bentley.PW--'
    $dsn = 'decide-pwce-us.bentley.com~3Adecide-pwce-us-10'
    $class = 'PW_WSG/Document'
    $id = '560ff1f5-bcab-4527-916b-6d240e0c45f8'

    # $downloadUrl = 'decide-pwce-us-ws.bentley.com/.../$file'

    $downloadUrl = "$wsgURL$dsn/$class/$id/" + '$file'

    # requires federated account
    $token2 = Get-PWConnectionClientToken -ConnectedProjectUser dave.brumbaugh@eagle.bentley.com -ConnectedProjectPassword (Read-Host -Prompt Password -AsSecureString)

    # just for information to see the underlying SAML
    ConvertFrom-EncodedToken $token2

    $random = Get-RandomString -Length 10 -Characters "abcdefghijklmnopqrstuvwxyz"

    Invoke-WebRequest -Method Get -Uri $downloadUrl -Headers @{Authorization = 'Token ' + $token2} -OutFile ("c:\temp\" + ($random) + ".pdf")

    # alternative method for connecting to WSG with logical user account
    $logicalToken = Get-EncodedLogicalToken -User "MyUser" -Password (Read-Host -Prompt Password -AsSecureString)

    $random = Get-RandomString -Length 10 -Characters "abcdefghijklmnopqrstuvwxyz"

    Invoke-WebRequest -Method Get -Uri $downloadUrl -Headers @{Authorization = 'Basic ' + $logicalToken} -OutFile ("c:\temp\" + ($random) + ".pdf")

Reply
  • I've just been doing some work in this area and it was pointed out that there has been no answer to this.Here's a very simple PowerShell script using WSG to download files. You will need at least version 1.11.2.0 of PWPS_DAB to run this script.

    $wsgURL = 'decide-pwce-us-ws.bentley.com/.../Bentley.PW--'
    $dsn = 'decide-pwce-us.bentley.com~3Adecide-pwce-us-10'
    $class = 'PW_WSG/Document'
    $id = '560ff1f5-bcab-4527-916b-6d240e0c45f8'

    # $downloadUrl = 'decide-pwce-us-ws.bentley.com/.../$file'

    $downloadUrl = "$wsgURL$dsn/$class/$id/" + '$file'

    # requires federated account
    $token2 = Get-PWConnectionClientToken -ConnectedProjectUser dave.brumbaugh@eagle.bentley.com -ConnectedProjectPassword (Read-Host -Prompt Password -AsSecureString)

    # just for information to see the underlying SAML
    ConvertFrom-EncodedToken $token2

    $random = Get-RandomString -Length 10 -Characters "abcdefghijklmnopqrstuvwxyz"

    Invoke-WebRequest -Method Get -Uri $downloadUrl -Headers @{Authorization = 'Token ' + $token2} -OutFile ("c:\temp\" + ($random) + ".pdf")

    # alternative method for connecting to WSG with logical user account
    $logicalToken = Get-EncodedLogicalToken -User "MyUser" -Password (Read-Host -Prompt Password -AsSecureString)

    $random = Get-RandomString -Length 10 -Characters "abcdefghijklmnopqrstuvwxyz"

    Invoke-WebRequest -Method Get -Uri $downloadUrl -Headers @{Authorization = 'Basic ' + $logicalToken} -OutFile ("c:\temp\" + ($random) + ".pdf")

Children
  • Hi Dave,

    Thank you for this workaround, it works but now my problem is to get the token, because I need to be able to use other language than powershell (like python).

    Currently my workaround is every week I have to use Get-PWConnectionClientToken from pwps_dab to get a token and store it in a file. Then I can read it from my script and use it.

    It's not convenient and not secure.

    It would be much better if I could directly get the token from Bentley IMS using oauth2 / imsoidc. But I'm very not familiar with that, and I didn't find any documentation from bentley.

    Our goal is to integrate our in-house applications with ProjectWise. And we want to do it using Rest API, because some of apps are desktop apps, but other are web apps.

  • We've had other people asking about this and I came up with this approach. This example is PowerShell, but I think will be pretty easy to port to Python. I have not had any luck once ADFS gets involved, but YMMV.

    The example gets a token that is valid for use with WSG for doing a file download and for passing to a regular API-based login.

    ---[PowerShell Source below]----

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $infoBit = ConvertTo-Json -Depth 4 @{
    AppliesTo = "">connect-bts.bentley.com"
    RequestType = "">schemas.microsoft.com/.../issue"
    KeyType = "">schemas.microsoft.com/.../bearer"
    }

    $Text = "Your.User@company.com:YourPassword"
    $Bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
    $EncodedText =[Convert]::ToBase64String($Bytes)

    $authVal = "Basic " + $EncodedText

    $imsUrl="">ims.bentley.com/.../IssueEx"

    $resp = Invoke-RestMethod -ContentType "Application/Json" -Method Post -Body $infoBit -Uri $imsUrl -Headers @{"AUTHORIZATION"=$authVal}

    $xmlDoc = New-Object System.Xml.XmlDocument
    $xmlDoc.LoadXml($resp.RequestedSecurityToken)

    $cert = $xmlDoc.SelectSingleNode("//*[local-name()='X509Certificate']")

    $base64EncodedCertificate = $cert.'#text'

    $imsDelegatedUrl="">ims.bentley.com/.../IssueEx"

    $authValueDelegated = "X509 access_token=" + $base64EncodedCertificate

    $infoBitDelegated = ConvertTo-Json -Depth 4 @{
    AppliesTo = "sso://pw-integration-server/1016"
    RequestType = "">schemas.microsoft.com/.../issue"
    KeyType = "">schemas.microsoft.com/.../bearer"
    ActAs = $resp.RequestedSecurityToken
    AppliesToBootstrapToken = "">connect-bts.bentley.com"
    }

    $resp2 = Invoke-RestMethod -ContentType "Application/Json" -Method Post -Body $infoBitDelegated -Uri $imsDelegatedUrl -Headers @{"AUTHORIZATION"=$authValueDelegated}

    #cmdlet from PWPS_DAB

    $encToken2 = ConvertTo-EncodedToken $resp2.RequestedSecurityToken

    $wsgURL = 'decide-pwce-us-ws.bentley.com/.../Bentley.PW--'
    $dsn = 'decide-pwce-us.bentley.com~3Adecide-pwce-us-10'
    $class = 'PW_WSG/Document'
    $id = '560ff1f5-bcab-4527-916b-6d240e0c45f8'

    #example URL

    # $downloadUrl = 'decide-pwce-us-ws.bentley.com/.../$file'

    $downloadUrl = "$wsgURL$dsn/$class/$id/" + '$file'

    #cmdlet from PWPS_DAB

    $random = Get-RandomString -Length 10 -Characters "abcdefghijklmnopqrstuvwxyz"

    Invoke-WebRequest -Method Get -Uri $downloadUrl -Headers @{Authorization = 'Token ' + $encToken2} -OutFile ("c:\temp\" + ($random) + ".pdf")

    #cmdlet from PWPS_DAB

    New-PWLogin "decide-pwce-us.bentley.com:decide-pwce-us-10" -BentleyIMS -Token $resp2.RequestedSecurityToken

    I am looking for more general approaches, but this is what I've gotten to at this point. I hope is helpful.

    Dave

  • I tested it, and I confirm that it works well... when you are not federated.

    We don't use ADFS but Azure AD for federation. The issue seems to be that ims.bentley.com request something like xml from the identity provider, instead of login / password credentials.

  • I'm not sure this will help but I am trying to do something parallel to other vendors using oaauth2 and STS in a Django environment.

    The authorisation package provides azure, google, facebook,.... out of the box. We had to cusomise for AutoDesk BIM360 and ArcGIS online but it was pretty straight forward.

    Here's the code to do third party login for autodesk on top of django.allauth:

    provider.py

    class AutodeskAccount(ProviderAccount):
        def get_profile_url(self):
            return self.account.extra_data.get('profileUrl')
    
        def get_avatar_url(self):
            return self.account.extra_data.get('avatar')
    
        def to_str(self):
            dflt = super(AutodeskAccount, self).to_str()
            return self.account.extra_data.get('username', dflt)
    
    
    class AutodeskProvider(OAuth2Provider):
        id = 'autodesk'
        name = 'Autodesk Forge'
        account_class = AutodeskAccount
        #['data.read', 'viewable.read', 'bucket:create', 'bucket:read', 'data:write'] # 'data.read', 'viewable.read']
        scope = ['data:read', 'viewables.read', 'bucket:create', 'bucket:read', 'data:write']
    
        def extract_uid(self, data):
            return str(data['userId'])
    
        def extract_common_fields(self, data):
            return dict(username=data.get('username'),
                        name=data.get('displayName'),
                        email=data.get('email'))
    
        def get_default_scope(self):
            scope = self.scope
            return scope
    
    providers.registry.register(AutodeskProvider)
    

    views.py

    class AutodeskOAuth2Adapter(OAuth2Adapter):
        provider_id = AutodeskProvider.id
        headers = {"User-Agent": "django-allauth-header"}
    
        access_token_url = 'https://developer.api.autodesk.com/authentication/v1/gettoken'
        authorize_url = 'https://developer.api.autodesk.com/authentication/v1/authorize'
        profile_url = 'https://developer.api.autodesk.com/userprofile/v1/users/@me'
    
    
        # After successfully logging in, use access token to retrieve user info
        def complete_login(self, request, app, token, **kwargs):
            headers = {"Authorization": "Bearer " + token.token}
            headers.update(self.headers)
            extra_data = requests.get(self.profile_url, headers=headers)
    
            # This only here because of weird response from the test suite
            if isinstance(extra_data, list):
                extra_data = extra_data[0]
    
            return self.get_provider().sociallogin_from_response(
                request,
                extra_data.json()
            )
    
    oauth2_login = OAuth2LoginView.adapter_view(AutodeskOAuth2Adapter)
    oauth2_callback = OAuth2CallbackView.adapter_view(AutodeskOAuth2Adapter)

  • Headed out on vacation, but I will delve into the Azure AD and ADFS problems when I return in a couple of weeks. Will share if I have any luck.

    Dave